diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 53c37a16..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index dd036706..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "env": { - "es2022": true, - "node": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 2022, - "sourceType": "module" - }, - "rules": { - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "linebreak-style": [ - "error", - "windows" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "never" - ], - "no-var": [ - "error" - ], - "no-console": [ - 0 - ], - "no-control-regex": [ - 0 - ], - "no-unused-vars": [ - "error", - { - "vars": "all", - "args": "none", - "ignoreRestSiblings": false, - "argsIgnorePattern": "reject" - } - ], - "no-async-promise-executor": [ - 0 - ] - }, - "overrides": [ - { - "files": [ "app/assets/js/scripts/*.js" ], - "rules": { - "no-unused-vars": [ - 0 - ], - "no-undef": [ - 0 - ] - } - } - ] -} \ No newline at end of file diff --git a/app/app.ejs b/app/app.ejs deleted file mode 100644 index e829fa14..00000000 --- a/app/app.ejs +++ /dev/null @@ -1,55 +0,0 @@ - - - - Helios Launcher - - - - - - - <%- include('frame') %> -
- <%- include('welcome') %> - <%- include('login') %> - <%- include('waiting') %> - <%- include('loginOptions') %> - <%- include('settings') %> - <%- include('landing') %> -
- <%- include('overlay') %> -
-
-
- - -
-
-
- - - \ No newline at end of file diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css deleted file mode 100644 index e95bcb29..00000000 --- a/app/assets/css/launcher.css +++ /dev/null @@ -1,3967 +0,0 @@ -/* Github Code Highlighting. */ -@import "../../../node_modules/github-syntax-dark/lib/github-dark.css"; - -/******************************************************************************* - * * - * Fonts * - * * - ******************************************************************************/ - -@font-face { - font-family: 'Avenir Book'; - src: url('../fonts/Avenir-Book.ttf'); -} - -@font-face { - font-family: 'Avenir Medium'; - src: url('../fonts/Avenir-Medium.ttf'); -} - -@font-face { - font-family: 'Ringbearer'; - src: url('../fonts/Ringbearer.ttf'); -} - -/******************************************************************************* - * * - * Element Styles * - * * - ******************************************************************************/ - -/* Reset body, html, and div presets. */ -body, html, div { - margin: 0px; - padding: 0px; -} - -/* Reset p presets. */ -p { - -webkit-margin-before: 0em; - -webkit-margin-after: 0em; -} - -/* Set default font and color. */ -body, button { - font-family: 'Avenir Book'; - color: white; -} - -/*body { - background: url('./../images/backgrounds/0.jpg') no-repeat center center fixed; - background-size: cover; -}*/ - -/******************************************************************************* - * * - * Frame Styles (frame.ejs) * - * * - ******************************************************************************/ - -/* Frame Bar */ -#frameBar { - position: relative; - z-index: 100; - display: flex; - flex-direction: column; - transition: background-color 1s ease; - /*background-color: rgba(0, 0, 0, 0.5);*/ - -webkit-user-select: none; -} - -/* Undraggable region on the top of the frame. */ -#frameResizableTop { - height: 2px; - width: 100%; - -webkit-app-region: no-drag; -} - -/* Flexbox to wrap the main frame content. */ -#frameMain { - display: flex; - height: 20px -} - -/* Undraggable region on the left and right of the frame. */ -.frameResizableVert { - width: 2px; - -webkit-app-region: no-drag; -} - -/* Main frame content for windows. */ -#frameContentWin { - display: flex; - justify-content: space-between; - width: 100%; - -webkit-app-region: drag; -} - -/* Main frame content for darwin. */ -#frameContentDarwin { - display: flex; - justify-content: flex-start; - align-items: center; - width: 100%; - -webkit-app-region: drag; -} - -/* Frame logo (windows only). */ -#frameTitleDock { - padding: 0px 10px; -} -#frameTitleText { - font-size: 14px; - font-family: 'Avenir Medium'; - letter-spacing: 0.5px; -} - -/* Windows frame button dock. */ -#frameButtonDockWin { - -webkit-app-region: no-drag !important; - position: relative; - top: -2px; - right: -2px; - height: 22px; -} -#frameButtonDockWin > .frameButton:not(:first-child) { - margin-left: -4px; -} - -/* Darwin frame button dock: NaN; */ -#frameButtonDockDarwin { - -webkit-app-region: no-drag !important; - position: relative; - top: -1px; - right: -1px; -} - -/* Windows Frame Button Styles. */ -.frameButton { - background: none; - border: none; - height: 22px; - width: 39px; - cursor: pointer; -} -.frameButton:hover, -.frameButton:focus { - background: rgba(189, 189, 189, 0.43); -} -.frameButton:active { - background: rgba(156, 156, 156, 0.43); -} -.frameButton:focus { - outline: 0px; -} - -/* Close button is red. */ -#frameButton_close:hover, -#frameButton_close:focus { - background: rgba(255, 53, 53, 0.61) !important; -} -#frameButton_close:active { - background: rgba(235, 0, 0, 0.61) !important; -} - -/* Darwin Frame Button Styles. */ -.frameButtonDarwin { - height: 12px; - width: 12px; - border-radius: 50%; - border: 0px; - margin-left: 5px; - -webkit-app-region: no-drag !important; - cursor: pointer; -} -.frameButtonDarwin:focus { - outline: 0px; -} - -#frameButtonDarwin_close { - background-color: #e74c32; -} -#frameButtonDarwin_close:hover, -#frameButtonDarwin_close:focus { - background-color: #FF9A8A; -} -#frameButtonDarwin_close:active { - background-color: #ff8d7b; -} - -#frameButtonDarwin_minimize { - background-color: #fed045; -} -#frameButtonDarwin_minimize:hover, -#frameButtonDarwin_minimize:focus { - background-color: #FFE9A9; -} -#frameButtonDarwin_minimize:active { - background-color: #ffde7b; -} - -#frameButtonDarwin_restoredown { - background-color: #96e734; -} -#frameButtonDarwin_restoredown:hover, -#frameButtonDarwin_restoredown:focus { - background-color: #D6FFA6; -} -#frameButtonDarwin_restoredown:active { - background-color: #bfff76; -} - -/******************************************************************************* - * * - * Welcome View (welcome.ejs) * - * * - ******************************************************************************/ - -#welcomeContainer { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - background: rgba(0, 0, 0, 0.50); -} - -#welcomeContent { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 50%; - top: -10%; - position: relative; -} - -/* -.cloudDiv { - position: absolute; - height: 100%; - width: 100%; - display: flex; - flex-direction: column; -} - -.cloudTop { - height: 50%; - width: 100%; - background-image: url('../images/cloudTrans.png'); - animation: clouds1 80s linear infinite; - background-size: cover; -} - -.cloudBottom { - height: 50%; - width: 100%; - background-image: url('../images/cloudTrans2.png'); - animation: clouds2 70s linear infinite; - background-size: cover; -} - -@keyframes clouds1 { - to { - background-position: 200%; - } -} -@keyframes clouds2 { - to { - background-position: 230%; - } -} -*/ - -#welcomeImageSeal { - border-radius: 50%; - border: 2px solid #cad7e1; - background: rgba(1, 2, 1, 0.5); - height: 125px; - width: 125px; - box-shadow: 0px 0px 10px 0px rgb(0, 0, 0); - margin-bottom: 5%; - margin-top: 10%; -} - -#welcomeHeader { - font-family: 'Avenir Medium'; - text-align: center; - color: white; - margin-bottom: 25px; - letter-spacing: 1px; - font-size: 20px; - text-shadow: white 0px 0px 0px; -} - -#welcomeDescription { - text-align: justify; - font-size: 13px; - font-weight: 100; - text-shadow: rgba(255, 255, 255, 0.75) 0px 0px 20px -} - -#welcomeDescCTA { - text-align: center; - font-size: 14px; - font-weight: 100; - text-shadow: rgba(255, 255, 255, 0.75) 0px 0px 20px -} - -/* Login button styles. */ -#welcomeButton { - background: none; - font-weight: bold; - letter-spacing: 2px; - border: none; - padding: 15px 5px; - margin: 10px 0px; - cursor: pointer; - position: relative; - right: -20px; - transition: 0.5s ease; - margin-top: 5%; - margin-bottom: -5%; -} -#welcomeButton:disabled { - color: rgba(255, 255, 255, 0.75); - pointer-events: none; -} -#welcomeButton:hover, -#welcomeButton:focus { - text-shadow: 0px 0px 20px #fff; - outline: none; -} -#welcomeButton:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7; -} -#welcomeSVG { - -webkit-transform: translate3d(0, 0, 0); - overflow: visible; - transform: rotate(90deg); - margin-left: 20px; - transition: 0.25s ease; - width: 20px; - height: 20px; -} -#welcomeButton:hover #welcomeSVG, -#welcomeButton:focus #welcomeSVG { - -webkit-filter: drop-shadow(0px 0px 2px #fff); -} -#welcomeButton:active #welcomeSVG .arrowLine { - stroke: #c7c7c7; -} -#welcomeButton:active #welcomeSVG { - -webkit-filter: drop-shadow(0px 0px 2px #c7c7c7); -} -#welcomeButton:disabled #welcomeSVG .arrowLine { - stroke: rgba(255, 255, 255, 0.75); -} - -#welcomeButtonContent { - display: flex; - align-items: center; -} - -/******************************************************************************* - * * - * Login View (login.ejs) * - * * - ******************************************************************************/ - -/* Styles for dimmer login span. */ -.loginSpanDim { - font-size: 12px; - color: #848484; - font-weight: bold; -} - -/* Main login container. */ -#loginContainer { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - transition: filter 0.25s ease; - background: rgba(0, 0, 0, 0.50); -} - -/* Login cancel button styles. */ -#loginCancelContainer { - position: absolute; - top: 5%; - right: 5%; -} - -/* Login cancel button styles. */ -#loginCancelButton { - background: none; - border: none; - outline: none; - cursor: pointer; - transition: 0.25s ease; -} -#loginCancelButton:hover #loginCancelIcon, -#loginCancelButton:hover #loginCancelText, -#loginCancelButton:focus #loginCancelIcon, -#loginCancelButton:focus #loginCancelText { - text-shadow: 0px 0px 20px white; -} -#loginCancelButton:hover #loginCancelIcon, -#loginCancelButton:focus #loginCancelIcon { - box-shadow: 0px 0px 20px white; -} -#loginCancelButton:active #loginCancelIcon, -#loginCancelButton:active #loginCancelText { - text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75); - color: rgba(255, 255, 255, 0.75); - border-color: rgba(255, 255, 255, 0.75); -} -#loginCancelButton:active #loginCancelIcon { - box-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75); -} -#loginCancelButton:disabled { - pointer-events: none; -} -#loginCancelButton:disabled #loginCancelIcon, -#loginCancelButton:disabled #loginCancelText { - color: rgba(255, 255, 255, 0.75); - border-color: rgba(255, 255, 255, 0.75); -} - -/* The X in a circle icon for the cancel button. */ -#loginCancelIcon { - border-radius: 50%; - border: 1px solid white; - box-sizing: border-box; - height: 30px; - width: 30px; - font-size: 19px; - line-height: 30px; - margin: 0 auto; - margin-bottom: 5px; - transition: 0.25s ease; -} -/* Text for the login cancel button. */ -#loginCancelText { - font-size: 15px; - transition: 0.25s ease; -} - -/* Login content wrapper. */ -#loginContent { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - padding: 0px 25px; -} - -/* Login form. */ -#loginForm { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -/* Login form anchor styles. */ -#loginForm a { - font-size: 12px; - color: #848484; - font-weight: bold; - text-decoration: none; - transition: 0.25s ease; -} -#loginForm a:hover, -#loginForm a:focus { - color: #a2a2a2; - outline: none; -} -#loginForm a:active { - color: #8b8b8b; -} - -/* Logo on login form. */ -#loginImageSeal { - border-radius: 50%; - border: 2px solid #cad7e1; - background: rgba(1, 2, 1, 0.5); - height: 125px; - width: 125px; - box-shadow: 0px 0px 10px 0px rgb(0, 0, 0); - margin-bottom: 20px; -} - -/* Header on login view. */ -#loginSubheader { - font-family: 'Avenir Medium'; - margin-bottom: 25px; - font-size: 12px; - letter-spacing: 1px; - font-weight: bold; -} - -/* Container to organize login field elements. */ -.loginFieldContainer { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -/* SVG icons on the login view. */ -.loginSVG { - fill: #fff; - height: 20px; - width: 20px; -} - -/* Span which displays errors related to login field content. */ -.loginErrorSpan { - font-family: 'Avenir Medium'; - font-weight: bold; - font-size: 8px; - color: #ff1b0c; - width: 100%; - text-align: right; - position: absolute; - top: 7px; - opacity: 0; - transition: 0.25s ease; -} - -.shake { - animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both; -} - -@keyframes shake { - 10%, 90% { - transform: translate3d(-1px, 0, 0); - } - - 20%, 80% { - transform: translate3d(2px, 0, 0); - } - - 30%, 50%, 70% { - transform: translate3d(-4px, 0, 0); - } - - 40%, 60% { - transform: translate3d(4px, 0, 0); - } -} - -/* Login text input styles. */ -.loginField { - font-family: 'Avenir Book'; - background: none; - border-width: 1.5px 0px 0px 0px; - border-style: solid; - width: 250px; - margin-bottom: 20px; - border-color: #fff; - color: rgba(255, 255, 255, 0.75); - font-weight: bold; - text-align: center; - box-sizing: border-box; - padding: 7.5px; - font-size: 10px; - letter-spacing: 1px; -} -.loginField:focus { - outline: none; -} -.loginField:disabled { - color: rgba(255, 255, 255, 0.50); -} -.loginField::-webkit-input-placeholder { - color: rgba(255, 255, 255, 0.75); - font-size: 10px; - letter-spacing: 1px; - text-align: center; - font-weight: bold; -} -.loginField:focus::-webkit-input-placeholder { - color: transparent; -} - -/* Add spacing between password field and options bar. */ -#labelPassword { - margin-bottom: 13px; -} - -/* Container which contains the forgot and remember options. */ -#loginOptions { - display: flex; - justify-content: space-between; - width: 100%; -} - -/* Remember option text. */ -#loginRememberText { - padding-right: 10px; - transition: 0.25s ease; -} - -/* Login button styles. */ -#loginButton { - background: none; - font-weight: bold; - letter-spacing: 2px; - border: none; - padding: 15px 5px; - margin: 10px 0px; - cursor: pointer; - position: relative; - right: -20px; - transition: 0.5s ease; -} -#loginButton:disabled { - color: rgba(255, 255, 255, 0.75); - pointer-events: none; -} -#loginButton[loading] { - color: #fff; -} -#loginButton:hover, -#loginButton:focus { - text-shadow: 0px 0px 20px #fff; - outline: none; -} -#loginButton:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7; -} -#loginSVG { - -webkit-transform: translate3d(0, 0, 0); - overflow: visible; - transform: rotate(90deg); - margin-left: 20px; - transition: 0.25s ease; - width: 20px; - height: 20px; -} -#loginButton:hover #loginSVG, -#loginButton:focus #loginSVG { - -webkit-filter: drop-shadow(0px 0px 2px #fff); -} -#loginButton:active #loginSVG .arrowLine { - stroke: #c7c7c7; -} -#loginButton:active #loginSVG { - -webkit-filter: drop-shadow(0px 0px 2px #c7c7c7); -} -#loginButton:disabled #loginSVG .arrowLine { - stroke: rgba(255, 255, 255, 0.75); -} - -#loginButtonContent { - display: flex; - align-items: center; -} - -#loginButton .circle-loader, -#loginButton[loading] #loginSVG { - display: none; -} -#loginButton[loading] .circle-loader, -#loginButton #loginSVG { - display: initial; -} - - -.circle-loader { - margin-left: 20px; - border: 2px solid rgba(255, 255, 255, 0.5); - border-left-color: #ffffff; - animation-name: loader-spin; - animation-duration: 1s; - animation-iteration-count: infinite; - animation-timing-function: linear; - position: relative; - display: inline-block; - vertical-align: top; - border-radius: 50%; - width: 16px; - height: 16px; -} -.load-complete { - animation: none; - border-color: #ffffff; - transition: border 500ms ease-out; -} -.checkmark { - display: none; -} -.checkmark.draw:after { - animation-duration: 800ms; - animation-timing-function: ease; - animation-name: checkmark; - transform: scaleX(-1) rotate(135deg); -} -.checkmark:after { - opacity: 1; - height: 8px; - width: 4px; - transform-origin: left top; - border-right: 2px solid #ffffff; - border-top: 2px solid #ffffff; - content: ''; - left: 2px; - top: 8px; - position: absolute; -} -@keyframes loader-spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} -@keyframes checkmark { - 0% { - height: 0; - width: 0; - opacity: 1; - } - 20% { - height: 0; - width: 4px; - opacity: 1; - } - 40% { - height: 8px; - width: 4px; - opacity: 1; - } - 100% { - height: 8px; - width: 4px; - opacity: 1; - } -} - - - -/*.spinningCircle { - margin-left: 20px; - height: 16px; - width: 16px; - border-radius: 50%; - border: 2px solid rgba(255,255,255,0); - border-top-color: #ffffff; - border-right-color: #ffffff; - border-left-color: rgba(255, 255, 255, 0.50); - border-bottom-color: rgba(255, 255, 255, 0.50); - animation: single2 4s infinite linear; -} - -@keyframes single2 { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(720deg); - } -}*/ - -/* Disclaimer container. */ -#loginDisclaimer { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -/* Add spacing between register anchor and disclaimer. */ -#loginRegisterSpan { - margin-bottom: 5px; -} - -/* Disclaimer text styles. */ -.loginDisclaimerText { - font-size: 7px; - color: #848484; - font-weight: bold; - text-align: center; -} - -/* * * -* Login View | Custom Checkbox -* * */ - -/* Checkbox container. */ -#checkmarkContainer { - display: flex; - justify-content: flex-end; - align-items: center; - position: relative; - cursor: pointer; - font-size: 22px; - -webkit-user-select: none; -} - -/* Hide the default checkbox. */ -#checkmarkContainer input { - opacity: 0; - cursor: pointer; - position: absolute; -} - -/* Create a custom checkbox. */ -.loginCheckmark { - position: relative; - height: 10px; - width: 10px; - border: 1px solid #848484; - border-radius: 1px; - background: none; - transition: 0.25s ease; -} -/* On hover and focus, add a grey border color. */ -#checkmarkContainer:hover input ~ *, -#checkmarkContainer input:focus ~ * { - color: #a2a2a2; - border-color: #a2a2a2; -} -/* On keydown, darken the checkbox a bit. */ -#checkmarkContainer input:active ~ *:not(#loginRememberText) { - color: #8d8d8d; - border-color: #8d8d8d; -} -#checkmarkContainer[disabled] { - pointer-events: none; -} -/* For checked -> #checkmarkContainer input:checked ~ * */ -/* Create the checkmark/indicator (hidden when not checked). */ -.loginCheckmark:after { - content: ""; - display: none; -} -/* Show the checkmark when checked. */ -#checkmarkContainer input:checked ~ .loginCheckmark:after { - display: block; -} -/* Style the checkmark/indicator. */ -#checkmarkContainer .loginCheckmark:after { - position: absolute; - left: 3.5px; - top: 0.5px; - width: 2px; - height: 6px; - border: solid #a2a2a2; - border-width: 0 2px 2px 0; - transform: rotate(45deg); -} - -/* -#login_filter { - height: calc(100% - 22px); - width: 100%; - z-index: 9000; - position: absolute; - filter: blur(8px) contrast(0.9) brightness(1.0); - background: url('./../images/backgrounds/0.jpg') no-repeat center center fixed; - transform: scale(1.2); - background-size: cover; -} -*/ - -/******************************************************************************* - * * - * Waiting View (waiting.ejs) * - * * - ******************************************************************************/ - -#waitingContainer { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - transition: filter 0.25s ease; - background: rgba(0, 0, 0, 0.50); -} - -#waitingContent { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 50%; - top: -10%; - position: relative; -} - -.waitingSpinner:before { - transform: rotateX(60deg) rotateY(45deg) rotateZ(45deg); - animation: 750ms rotateBefore infinite linear reverse; -} -.waitingSpinner:after { - transform: rotateX(240deg) rotateY(45deg) rotateZ(45deg); - animation: 750ms rotateAfter infinite linear; -} -.waitingSpinner:before, -.waitingSpinner:after { - box-sizing: border-box; - content: ''; - display: block; - position: fixed; - top: calc(50% - 5em); - /* left: 50%; */ - margin-top: -5em; - margin-left: -5em; - width: 10em; - height: 10em; - transform-style: preserve-3d; - transform-origin: 50%; - transform: rotateY(50%); - perspective-origin: 50% 50%; - perspective: 340px; - background-size: 10em 10em; - background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI2NnB4IiBoZWlnaHQ9IjI5N3B4IiB2aWV3Qm94PSIwIDAgMjY2IDI5NyIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+CiAgICA8dGl0bGU+c3Bpbm5lcjwvdGl0bGU+CiAgICA8ZGVzY3JpcHRpb24+Q3JlYXRlZCB3aXRoIFNrZXRjaCAoaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoKTwvZGVzY3JpcHRpb24+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4KICAgICAgICA8cGF0aCBkPSJNMTcxLjUwNzgxMywzLjI1MDAwMDM4IEMyMjYuMjA4MTgzLDEyLjg1NzcxMTEgMjk3LjExMjcyMiw3MS40OTEyODIzIDI1MC44OTU1OTksMTA4LjQxMDE1NSBDMjE2LjU4MjAyNCwxMzUuODIwMzEgMTg2LjUyODQwNSw5Ny4wNjI0OTY0IDE1Ni44MDA3NzQsODUuNzczNDM0NiBDMTI3LjA3MzE0Myw3NC40ODQzNzIxIDc2Ljg4ODQ2MzIsODQuMjE2MTQ2MiA2MC4xMjg5MDY1LDEwOC40MTAxNTMgQy0xNS45ODA0Njg1LDIxOC4yODEyNDcgMTQ1LjI3NzM0NCwyOTYuNjY3OTY4IDE0NS4yNzczNDQsMjk2LjY2Nzk2OCBDMTQ1LjI3NzM0NCwyOTYuNjY3OTY4IC0yNS40NDkyMTg3LDI1Ny4yNDIxOTggMy4zOTg0Mzc1LDEwOC40MTAxNTUgQzE2LjMwNzA2NjEsNDEuODExNDE3NCA4NC43Mjc1ODI5LC0xMS45OTIyOTg1IDE3MS41MDc4MTMsMy4yNTAwMDAzOCBaIiBpZD0iUGF0aC0xIiBmaWxsPSIjZmZmZmZmIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPg==); -} - -#waitingTextContainer { - position: fixed; - top: 50%; -} - -@keyframes rotateBefore { - from { - transform: rotateX(60deg) rotateY(45deg) rotateZ(0deg); - } - to { - transform: rotateX(60deg) rotateY(45deg) rotateZ(-360deg); - } -} - -@keyframes rotateAfter { - from { - transform: rotateX(240deg) rotateY(45deg) rotateZ(0deg); - } - to { - transform: rotateX(240deg) rotateY(45deg) rotateZ(360deg); - } -} - -/******************************************************************************* - * * - * Login Options View (loginOptions.ejs) * - * * - ******************************************************************************/ - -#loginOptionsContainer { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - transition: filter 0.25s ease; - background: rgba(0, 0, 0, 0.50); -} - -#loginOptionsContent { - border-radius: 3px; - position: relative; - top: -5%; -} - -.loginOptionsMainContent { - display: flex; - flex-direction: column; - align-items: center; -} - -.loginOptionActions { - display: flex; - flex-direction: column; - row-gap: 10px; -} - -.loginOptionButtonContainer { - width: 16em; -} - -.loginOptionButton { - background: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - height: 50px; - width: 100%; - text-align: left; - padding: 0px 25px; - cursor: pointer; - outline: none; - transition: 0.25s ease; - display: flex; - align-items: center; - column-gap: 5px; -} -.loginOptionButton:hover, -.loginOptionButton:focus { - background: rgba(54, 54, 54, 0.25); - text-shadow: 0px 0px 20px white; -} - -#loginOptionCancelContainer { - position: absolute; - bottom: -100px; -} - -#loginOptionCancelButton { - background: none; - border: none; - padding: 2px 0px; - font-size: 16px; - font-weight: bold; - color: lightgrey; - cursor: pointer; - outline: none; - transition: 0.25s ease; -} -#loginOptionCancelButton:hover, -#loginOptionCancelButton:focus { - text-shadow: 0px 0px 20px lightgrey; -} -#loginOptionCancelButton:active { - text-shadow: 0px 0px 20px rgba(211, 211, 211, 0.75); - color: rgba(211, 211, 211, 0.75); -} -#loginOptionCancelButton:disabled { - color: rgba(211, 211, 211, 0.75); - pointer-events: none; -} - - -/******************************************************************************* - * * - * Settings View (sttings.ejs) * - * * - ******************************************************************************/ - -/* Main settings container. */ -#settingsContainer { - position: relative; - height: 100%; - display: flex; - background-color: rgba(0, 0, 0, 0.50); - transition: background-color 0.25s cubic-bezier(.02, .01, .47, 1); -} - -/* Drop shadow displayed when content is scrolled out of view. */ -#settingsContainer:before { - content: ''; - background: linear-gradient(rgba(0, 0, 0, 0.25), transparent); - width: 100%; - height: 5px; - position: absolute; - opacity: 0; - transition: opacity 0.25s ease; -} -#settingsContainer[scrolled]:before { - opacity: 1; -} - -/* Left hand side of the settings UI, for navigation. */ -#settingsContainerLeft { - padding-top: 4%; - height: 100%; - width: 25%; - box-sizing: border-box; -} - -/* Settings navigation container. */ -#settingsNavContainer { - height: 100%; - display: flex; - flex-direction: column; -} - -/* Navigation header styles. */ -#settingsNavHeader { - height: 15%; - display: flex; - justify-content: center; -} -#settingsNavHeaderText { - font-size: 20px; -} - -/* Navigation items outer container. */ -#settingsNavItemsContainer { - height: 85%; - display: flex; - justify-content: center; - box-sizing: border-box; -} - -/* Navigation items content container. */ -#settingsNavItemsContent { - height: 100%; - display: flex; - flex-direction: column; - position: relative; -} - -/* Navigation item shared styles. */ -.settingsNavItem { - background: none; - border: none; - text-align: left; - margin: 5px 0px; - padding: 0px 20px; - color: grey; - cursor: pointer; - outline: none; - transition: 0.25s ease; -} -.settingsNavItem:hover, -.settingsNavItem:focus { - color: #c1c1c1; - text-shadow: 0px 0px 20px #c1c1c1; -} -.settingsNavItem[selected] { - cursor: default; - color: white; - text-shadow: none; -} - -/* Div to add some space between nav items. */ -.settingsNavSpacer { - height: 25px; -} - -/* Content container for the done button. */ -#settingsNavContentBottom { - position: absolute; - top: 65%; -} - -/* Settings navigational divider. */ -.settingsNavDivider { - width: 75%; - height: 1px; - background: rgba(126, 126, 126, 0.57); - margin-left: auto; - margin-bottom: 25px; -} - -/* Settings done button styles. */ -#settingsNavDone { - background: none; - border: none; - text-align: left; - margin: 5px 0px; - padding: 0px 20px; - color: white; - cursor: pointer; - outline: none; - transition: 0.25s ease; -} -#settingsNavDone:hover, -#settingsNavDone:focus { - text-shadow: 0px 0px 20px white, 0px 0px 20px white, 0px 0px 20px white; -} -#settingsNavDone:active { - text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75); - color: rgba(255, 255, 255, 0.75); -} -#settingsNavDone:disabled { - color: rgba(255, 255, 255, 0.75); - pointer-events: none; -} - -/* Right hand side of the settings container, for tabs. */ -#settingsContainerRight { - height: 100%; - width: 75%; - box-sizing: border-box; -} - -/* Settings tab shared styles. */ -.settingsTab { - width: 100%; - height: 100%; - overflow-y: auto; -} -.settingsTab::-webkit-scrollbar { - width: 2px; -} -.settingsTab::-webkit-scrollbar-track { - display: none; -} -.settingsTab::-webkit-scrollbar-thumb { - border-radius: 10px; - box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.50); -} - -/* Add spacing to the top of each settings tab. */ -.settingsTab > *:first-child { - margin-top: 5%; -} - -/* Add spacing to the bottom of each settings tab. */ -.settingsTab > *:last-child { - margin-bottom: 20%; -} - -/* Tab header shared styles. */ -.settingsTabHeader { - display: flex; - flex-direction: column; - margin-bottom: 20px; -} -.settingsTabHeaderText { - font-size: 20px; - font-family: 'Avenir Medium'; -} -.settingsTabHeaderDesc { - font-size: 12px; -} - -/* Selected server content container */ -.settingsSelServContainer { - background: rgba(0, 0, 0, 0.25); - width: 75%; - border-radius: 3px; - display: flex; - justify-content: space-between; - margin: 15px 0px; -} - -/* Div which will be populated with the selected server's information. */ -.settingsSelServContent { - display: flex; - align-items: center; - justify-content: flex-start; - padding: 5px 0px; -} - -/* Wrapper container for the switch server button. */ -.settingsSwitchServerContainer { - display: flex; - align-items: center; - padding: 15px; -} - -/* Button to switch server configurations on the mods tab. */ -.settingsSwitchServerButton { - opacity: 0; - border: 1px solid rgb(255, 255, 255); - color: rgb(255, 255, 255); - background: none; - font-size: 12px; - border-radius: 3px; - font-family: 'Avenir Medium'; - transition: 0.25s ease; - cursor: pointer; - outline: none; -} -.settingsSwitchServerButton:hover, -.settingsSwitchServerButton:focus { - box-shadow: 0px 0px 20px rgb(255, 255, 255); - background: rgba(255, 255, 255, 0.25); -} -.settingsSwitchServerButton:active { - box-shadow: 0px 0px 20px rgb(187, 187, 187); - background: rgba(187, 187, 187, 0.25); - border: 1px solid rgb(187, 187, 187); - color: rgb(187, 187, 187); -} -.settingsSelServContainer:hover .settingsSwitchServerButton { - opacity: 1; -} - -/* Remove spin button from number inputs. */ -#settingsContainer input[type=number]::-webkit-inner-spin-button { - -webkit-appearance: none; -} - -/* Default styles for text/number inputs. */ -#settingsContainer input[type=number], -#settingsContainer input[type=text] { - color: white; - background: rgba(0, 0, 0, 0.25); - border-radius: 3px; - border: 1px solid rgba(126, 126, 126, 0.57); - font-family: 'Avenir Book'; - transition: 0.25s ease; -} -#settingsContainer input[type=number]:focus, -#settingsContainer input[type=text]:focus { - outline: none; - border-color: rgba(126, 126, 126, 0.87); -} -#settingsContainer input[type=number][error] { - border-color: rgb(255, 27, 12); - background: rgba(236, 0, 0, 0.25); - color: rgb(255, 27, 12); -} - -/* Styles for a generic settings entry. */ -.settingsFieldContainer { - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 0px; - width: 75%; - border-bottom: 1px solid rgba(255, 255, 255, 0.50); -} -.settingsFieldLeft { - display: flex; - flex-direction: column; -} -.settingsFieldTitle { - font-size: 14px; - font-family: 'Avenir Medium'; - color: rgba(255, 255, 255, 0.95); -} -.settingsFieldDesc { - font-size: 12px; - color: rgba(255, 255, 255, .95); - margin-top: 5px; -} -.settingsDivider { - height: 1px; - width: 75%; - background: rgba(255, 255, 255, 0.25); -} - -/* Toggle Switch */ -.toggleSwitch { - position: relative; - display: inline-block; - width: 40px; - height: 20px; - border-radius: 50px; - box-sizing: border-box; -} -.toggleSwitch input { - display:none; -} -.toggleSwitchSlider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.35); - transition: .4s; - border-radius: 50px; - border: 1px solid rgba(126, 126, 126, 0.57); -} -.toggleSwitchSlider:before { - position: absolute; - content: ""; - height: 13px; - width: 16px; - left: 3px; - bottom: 3px; - background-color: white; - box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.75); - border-radius: 50px; - transition: .4s; -} -input:checked + .toggleSwitchSlider { - background-color: rgb(31, 140, 11); - /* box-shadow: inset 2px 1px 20px black; */ - border: 1px solid rgb(31, 140, 11); -} -input:checked + .toggleSwitchSlider:before { - transform: translateX(15px); -} - -/* Range Slider styles. */ -.rangeSlider { - width: 35%; - height: 5px; - margin: 15px 0px; - background: grey; - border-radius: 3px; - position: relative; -} -.rangeSliderBar { - position: absolute; - background: #8be88b; - width: 50%; - height: 5px; - border-radius: 3px 0px 0px 3px; - transition: background 0.25s ease; -} -.rangeSliderTrack { - position: absolute; - top: -7.5px; - width: 7px; - height: 20px; - background: white; - border-radius: 3px; - left: 50%; - cursor: ew-resize; -} - -/* File selectors */ - -/* Main container for File selectors. */ -.settingsFileSelContainer { - display: flex; - flex-direction: column; - border-bottom: 1px solid rgba(255, 255, 255, 0.50); - margin-bottom: 20px; - margin-top: 20px; - width: 75%; -} - -/* File selector title. */ -.settingsFileSelTitle { - margin-bottom: 10px; -} - -/* Wrapper container for the actionable elements. */ -.settingsFileSelActions { - display: flex; - width: 90%; -} - -/* File selector icon settings. */ -.settingsFileSelIcon { - display: flex; - align-items: center; - background: rgba(126, 126, 126, 0.57); - border-radius: 3px 0px 0px 3px; - padding: 5px; - transition: 0.25s ease; -} -.settingsFileSelSVG { - width: 20px; - height: 20px; - fill: white; -} - -/* Disabled text field which stores the selected file path. */ -.settingsFileSelVal { - border-radius: 0px !important; - width: 100%; - padding: 5px 10px; - font-size: 12px; - height: 30px; -} - -/* File selection button. */ -.settingsFileSelButton { - border: 0px; - border-radius: 0px 3px 3px 0px; - font-size: 12px; - padding: 0px 5px; - cursor: pointer; - background: rgba(126, 126, 126, 0.57); - transition: 0.25s ease; - white-space: nowrap; - outline: none; -} -.settingsFileSelButton:hover, -.settingsFileSelButton:focus { - text-shadow: 0px 0px 20px white; -} -.settingsFileSelButton:active { - text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75); - color: rgba(255, 255, 255, 0.75); -} - -/* Description for the file selector. */ -.settingsFileSelDesc { - font-size: 10px; - margin: 20px 0px; - color: lightgrey; - width: 89%; -} -.settingsFileSelDesc strong { - font-family: 'Avenir Medium'; -} - -/* * * -* Settings View (Account Tab) -* * */ - -.settingsAuthAccountTypeContainer { - display: flex; - width: 75%; - flex-direction: column; -} - -.settingsAuthAccountTypeHeader { - display: flex; - align-items: center; - width: 100%; - justify-content: space-between; - padding: 10px 0px; - border-bottom: 1px solid #ffffff85; - margin-bottom: 30px; -} - -.settingsAuthAccountTypeHeaderLeft { - display: flex; - column-gap: 5px; -} - -/* Settings add account button styles. */ -.settingsAddAuthAccount { - background: none; - border: none; - text-align: left; - padding: 2px 0px; - color: white; - cursor: pointer; - outline: none; - transition: 0.25s ease; -} -.settingsAddAuthAccount:hover, -.settingsAddAuthAccount:focus { - text-shadow: 0px 0px 20px white, 0px 0px 20px white, 0px 0px 20px white; -} -.settingsAddAuthAccount:active { - text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75); - color: rgba(255, 255, 255, 0.75); -} -.settingsAddAuthAccount:disabled { - color: rgba(255, 255, 255, 0.75); - pointer-events: none; -} - -/* Auth account list container styles. */ -.settingsCurrentAccounts { - margin-bottom: 5%; -} -.settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) { - margin-bottom: 10px; -} -.settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) { - margin-top: 10px; -} - -/* Auth account shared styles. */ -.settingsAuthAccount { - display: flex; - background: rgba(0, 0, 0, 0.25); - border-radius: 3px; - border: 1px solid rgba(126, 126, 126, 0.57); -} - -/* Left hand side of an auth account element, for the skin image. */ -.settingsAuthAccountLeft { - padding: 5px 5px 5px 20px; -} - -/* Image of the auth account's skin. */ -.settingsAuthAccountImage { - height: 115px; -} - -/* Right hand side of the auth account, for info + actions. */ -.settingsAuthAccountRight { - display: flex; - width: 100%; -} - -/* Account details container. */ -.settingsAuthAccountDetails { - display: flex; - flex-direction: column; - justify-content: center; - margin-left: 20px; - width: 100%; -} -.settingsAuthAccountDetails > *:not(:last-child) { - margin-bottom: 20px; -} - -/* Account detail element styles. */ -.settingsAuthAccountDetailPane { - display: flex; - flex-direction: column; -} -.settingsAuthAccountDetailTitle { - font-size: 12px; - color: grey; - font-weight: bold; - font-family: 'Avenir Medium'; -} -.settingsAuthAccountDetailValue { - font-size: 14px; - -webkit-user-select: initial; -} - -/* Account actions container. */ -.settingsAuthAccountActions { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: flex-end; - padding: 10px; -} - -/* Account select button shared styles. */ -.settingsAuthAccountSelect { - opacity: 0; - border: none; - white-space: nowrap; - background: none; - font-family: 'Avenir Medium'; - outline: none; - transition: 0.25s ease; -} -.settingsAuthAccountSelect:hover:not([selected]), -.settingsAuthAccountSelect:focus:not([selected]) { - text-shadow: 0px 0px 20px white, 0px 0px 20px white; - cursor: pointer; -} -.settingsAuthAccount:hover .settingsAuthAccountSelect:not([selected]), -.settingsAuthAccountSelect[selected] { - opacity: 1; -} -.settingsAuthAccountSelect[selected] { - pointer-events: none; -} - -/* Account logout button shared styles. */ -.settingsAuthAccountLogOut { - opacity: 0; - border: 1px solid rgb(241, 55, 55); - color: rgb(241, 55, 55); - background: none; - font-size: 12px; - border-radius: 3px; - font-family: 'Avenir Medium'; - transition: 0.25s ease; - cursor: pointer; - outline: none; -} -.settingsAuthAccountLogOut:hover, -.settingsAuthAccountLogOut:focus { - box-shadow: 0px 0px 20px rgb(241, 55, 55); - background: rgba(241, 55, 55, 0.25); -} -.settingsAuthAccountLogOut:active { - box-shadow: 0px 0px 20px rgb(185, 47, 47); - background: rgba(185, 47, 47, 0.25); - border: 1px solid rgb(185, 47, 47); - color: rgb(185, 47, 47); -} -.settingsAuthAccount:hover .settingsAuthAccountLogOut { - opacity: 1; -} - -/* * * -* Settings View (Minecraft Tab) -* * */ - -/* Game resolution UI elements. */ -#settingsGameResolutionContainer { - display: flex; - flex-direction: column; - padding-bottom: 20px; - border-bottom: 1px solid rgba(255, 255, 255, 0.50); - width: 75%; -} -#settingsGameResolutionContent { - display: flex; - align-items: center; - padding-top: 10px; -} -#settingsGameResolutionCross { - color: grey; - padding: 0px 15px; -} -#settingsGameWidth, -#settingsGameHeight { - padding: 7.5px 5px; - width: 75px; -} - -/* * * -* Settings View (Mods Tab) -* * */ - -/* Main content container for the mod elements. */ -#settingsModsContainer { - width: 75%; -} - -/* Mod sub-container header text. */ -.settingsModsHeader { - padding-bottom: 10px; - border-bottom: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 10px; -} - -/* Mod elements sub-containers. */ -#settingsReqModsContainer, -#settingsOptModsContainer, -#settingsDropinModsContainer { - padding-bottom: 25px; -} - -/* Main content containers for mod elements. */ -#settingsReqModsContent, -#settingsOptModsContent, -#settingsDropinModsContent { - font-size: 12px; - background: rgba(0, 0, 0, 0.25); - border-radius: 3px; - color: white; -} - -/* Mod elements. */ -.settingsMod, -.settingsDropinMod { - padding: 10px; -} -.settingsSubMod { - padding: 10px 0px 10px 15px; - margin-left: 20px; - border-left: 1px solid rgba(255, 255, 255, 0.5); -} - -/* Main content container for mod element information. */ -.settingsModContent { - display: flex; - align-items: center; - justify-content: space-between; - transition: opacity 0.25s ease; -} - -/* Wrapper container for the left side of a mod element. */ -.settingsModMainWrapper { - display: flex; - align-items: center; -} - -/* Mod enabled/disabled status. */ -.settingsModStatus { - width: 7px; - height: 7px; - border-radius: 50%; - background-color: #c32625; - margin-right: 15px; - transition: 0.25s ease; -} - -/* Mod details container. */ -.settingsModDetails { - display: flex; - flex-direction: column; -} - -/* The version of the mod. */ -.settingsModVersion { - color: grey; - font-size: 10px; -} - -/* Disabled toggleswitch for required mods. */ -.toggleSwitch[reqmod] { - filter: grayscale(49%) brightness(60%); - pointer-events: none; -} - -/* Set the status color of an enabled mod. */ -.settingsBaseMod[enabled] > .settingsModContent > .settingsModMainWrapper > .settingsModStatus { - background-color: rgb(165, 195, 37); -} - -/* Add opacity to submods of a disabled mod. */ -.settingsBaseMod:not([enabled]) > .settingsSubModContainer .settingsModContent { - opacity: 0.5; -} - -/* Curve the left border for submods. */ -.settingsSubModContainer > .settingsSubMod:first-child { - border-top-left-radius: 10px; -} -.settingsSubModContainer > .settingsSubMod:last-child { - border-bottom-left-radius: 10px; -} -.settingsSubModContainer > .settingsSubMod:only-child { - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; -} - -/* Wrapper container for all submods. */ -.settingsSubModContainer { - margin-top: 10px; -} - -/* Button to open the mods folder for drop-in mods. */ -#settingsDropinFileSystemButton { - background: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - height: 50px; - width: 100%; - text-align: left; - padding: 0px 50px; - cursor: pointer; - outline: none; - transition: 0.25s ease; - margin-bottom: 10px; -} -#settingsDropinFileSystemButton:hover, -#settingsDropinFileSystemButton:focus, -#settingsDropinFileSystemButton[drag] { - background: rgba(54, 54, 54, 0.25); - text-shadow: 0px 0px 20px white; -} -/* Refresh instructions on the file system button. */ -#settingsDropinRefreshNote { - font-size: 10px; - pointer-events: none; -} - -/* Button to remove drop-in mods. */ -.settingsDropinRemoveButton { - background: none; - border: none; - font-size: 10px; - text-align: left; - padding: 0px; - color: #c32625; - font-weight: bold; - cursor: pointer; - outline: none; - transition: 0.25s ease; -} -.settingsDropinRemoveButton:hover, -.settingsDropinRemoveButton:focus { - text-shadow: 0px 0px 20px #c32625, 0px 0px 20px #c32625, 0px 0px 20px #c32625; -} -.settingsDropinRemoveButton:active { - color: #9b1f1f; - text-shadow: 0px 0px 20px #9b1f1f, 0px 0px 20px #9b1f1f, 0px 0px 20px #9b1f1f; -} - -/* Shaderpack settings description. */ -#settingsShaderpackDesc { - font-size: 10px; - margin: 10px 0px; - color: lightgrey; - font-weight: bold; - width: 89%; -} - -/* Wrapper container. */ -#settingsShaderpackWrapper { - display: flex; -} - -/* Button to add shaderpacks. */ -#settingsShaderpackButton { - background: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - cursor: pointer; - outline: none; - transition: 0.25s ease; - font-size: 14px; - padding: 6px 11px; - margin-right: 5px; -} -#settingsShaderpackButton:hover, -#settingsShaderpackButton:focus, -#settingsShaderpackButton[drag] { - background: rgba(54, 54, 54, 0.25); - text-shadow: 0px 0px 20px white; -} - -/* Main select container. */ -.settingsSelectContainer { - position: relative; - width: 50%; -} - -/* Div which displays the selected option. */ -.settingsSelectSelected { - border-radius: 3px; - border-width: 1px; - font-size: 14px; - padding: 6px 16px; -} - -/* Style the arrow inside the select element. */ -.settingsSelectSelected:after { - position: absolute; - content: ""; - top: calc(50% - 3px); - right: 10px; - width: 0; - height: 0; - border: 6px solid transparent; - border-color: rgba(126, 126, 126, 0.57) transparent transparent transparent; -} - -/* Point the arrow upwards when the select box is open (active). */ -.settingsSelectSelected.select-arrow-active:after { - border-color: transparent transparent rgba(126, 126, 126, 0.57) transparent; - top: 7px; -} -.settingsSelectSelected.select-arrow-active { - border-radius: 3px 3px 0px 0px; -} - -/* Options content container. */ -.settingsSelectOptions { - position: absolute; - top: 100%; - left: 0; - right: 0; - z-index: 99; - max-height: 300%; - overflow-y: scroll; - border: 1px solid rgba(126, 126, 126, 0.57); - border-top: none; - border-radius: 0px 0px 3px 3px; -} -/* Hide the items when the select box is closed. */ -.settingsSelectOptions[hidden] { - display: none; -} -.settingsSelectOptions::-webkit-scrollbar { - width: 2px; -} -.settingsSelectOptions::-webkit-scrollbar-track { - display: none; -} -.settingsSelectOptions::-webkit-scrollbar-thumb { - border-radius: 10px; - box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.50); -} - -/* Shared styles between options and selection div. */ -.settingsSelectOptions div, -.settingsSelectSelected { - background: rgba(0, 0, 0, 0.25); - border-style: solid; - border-color: rgba(126, 126, 126, 0.57); - color: #ffffff; - cursor: pointer; -} -.settingsSelectOptions div { - border-width: 0px 0px 1px 0px; - font-size: 12px; - padding: 4px 16px; -} -.settingsSelectOptions div:last-child { - border-bottom: none; -} - -/* Hover + selected styles. */ -.settingsSelectOptions div:hover, .settingsSelectOptions div[selected] { - background-color: rgba(255, 255, 255, 0.25) !important; -} - -/* * * -* Settings View (Java Tab) -* * */ - -/* Style links on the Java tab. */ -#settingsTabJava a, -.settingsChangelogText a { - color: rgba(202, 202, 202, 0.75); - transition: 0.25s ease; - outline: none; -} -#settingsTabJava a:hover, -#settingsTabJava a:focus, -.settingsChangelogText a:hover, -.settingsChangelogText a:focus { - color: rgba(255, 255, 255, 0.75); -} -#settingsTabJava a:active, -.settingsChangelogText a:active { - color: rgba(165, 165, 165, 0.75); -} - -/* Main container for memory management. */ -#settingsMemoryContainer { - width: 75%; - display: flex; - flex-direction: column; - border-bottom: 1px solid rgba(255, 255, 255, 0.50); - margin-bottom: 20px; -} - -/* Memory management title. */ -#settingsMemoryTitle { - margin-bottom: 10px; - padding-bottom: 5px; - border-bottom: 1px solid rgba(255, 255, 255, 0.5); -} - -/* Memory management content. */ -#settingsMemoryContent { - display: flex; - justify-content: space-between; - width: 100%; -} -#settingsMemoryContentLeft { - width: 69%; -} -#settingsMemoryContentRight { - display: flex; - align-items: center; - margin-right: 10%; -} - -/* Header for memory sliders. */ -.settingsMemoryHeader { - font-size: 14px; -} - -/* Wrapper container for a memory slider and label. */ -.settingsMemoryActionContainer { - display: flex; - align-items: center; - justify-content: space-between; -} - -/* Label which displays a memory slider's value. */ -.settingsMemoryLabel { - font-size: 14px; - margin-right: 2%; -} - -/* Range sliders for min and max memory settings. */ -#settingsMaxRAMRange, -#settingsMinRAMRange { - width: 85%; -} - -/* Memory status elements. */ -#settingsMemoryStatus { - display: flex; - flex-direction: column; -} -#settingsMemoryStatus > .settingsMemoryStatusContainer:not(:last-child){ - margin-bottom: 50%; -} -.settingsMemoryStatusContainer { - display: flex; - flex-direction: column; - align-items: center; -} -.settingsMemoryStatusTitle { - font-size: 12px; - color: grey; - font-weight: bold; -} -.settingsMemoryStatusValue { - color: lightgrey; - font-size: 16px; -} - -/* Description for memory management. */ -#settingsMemoryDesc { - font-size: 10px; - margin: 20px 0px; - color: lightgrey; - font-weight: bold; -} - -/* Status text which displays details on the selected executable. */ -#settingsJavaExecDetails { - font-weight: bold; - color: grey; - font-size: 12px; -} - -/* Main container for the JVM options setting. */ -#settingsJVMOptsContainer { - width: 75%; -} - -/* JVM options title. */ -#settingsJVMOptsTitle { - margin-bottom: 10px; -} - -/* Wrapper container for the actionable elements. */ -#settingsJVMOptsContent { - display: flex; - width: 90%; -} - -/* Text field to input the JVM options. */ -#settingsJVMOptsVal { - border-radius: 0px 3px 3px 0px !important; - width: 100%; - padding: 5px 10px; - font-size: 12px; -} -#settingsJVMOptsContent:focus-within > .settingsJavaIcon { - background: rgba(126, 126, 126, 0.87); -} - -/* Description for the JVM options setting. */ -#settingsJVMOptsDesc { - font-size: 10px; - margin: 20px 0px; - color: lightgrey; - font-weight: bold; - width: 89%; -} - -/* * * -* Settings View (Launcher Tab) -* * */ - -/* Tailored style for the data directory header. */ -#settingsDataDirTitle { - margin-bottom: 10px; -} - -/* * * -* Settings View (About Tab) -* * */ - -/* Main about content container. */ -#settingsAboutCurrentContainer { - display: flex; - flex-direction: column; - background: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - width: 75%; - margin-bottom: 20px; -} - -/* About content. */ -#settingsAboutCurrentContent { - display: flex; - flex-direction: column; - padding: 15px; -} - -/* About header elements. */ -#settingsAboutCurrentHeadline { - display: flex; - align-items: center; - padding-bottom: 5px; - border-bottom: 1px solid rgba(126, 126, 126, 0.57); -} -#settingsAboutLogo { - width: 30px; - height: 30px; - padding: 5px; -} -#settingsAboutTitle { - font-size: 23px; - padding-left: 10px; -} - -/* Current version container. */ -#settingsAboutCurrentVersion { - display: flex; - align-items: center; - padding-top: 10px; -} - -/* Checkmark next to the version information. */ -#settingsAboutCurrentVersionCheck { - border-radius: 50%; - background: #23aa23; - text-align: center; - font-weight: bold; - margin: 11px 12px; - color: white; - height: 15px; - width: 15px; - font-size: 12px; - line-height: 17px; -} - -/* Current version details container. */ -#settingsAboutCurrentVersionDetails { - margin-left: 10px; -} - -/* Release type text. */ -#settingsAboutCurrentVersionTitle { - font-size: 12px; - font-family: 'Avenir Medium'; - color: #23aa23; - font-weight: bold; -} - -/* Current version text. */ -#settingsAboutCurrentVersionLine { - font-size: 10px; - color: grey; - font-weight: bold; -} - -/* About information links. */ -#settingsAboutButtons { - display: flex; - padding: 0px 15px; - margin-bottom: 5px; -} -.settingsAboutButton { - background: none; - border: none; - font-size: 10px; - color: grey; - padding: 0px 5px; - transition: 0.25s ease; - outline: none; - text-decoration: none; -} -.settingsAboutButton:hover, -.settingsAboutButton:focus { - color: rgb(165, 165, 165); -} -.settingsAboutButton:active { - color: rgba(124, 124, 124, 0.75); -} - -/* Main changelog container. */ -.settingsChangelogContainer { - display: flex; - flex-direction: column; - background: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - width: 75%; - margin-bottom: 20px; -} - -/* Changelog content container. */ -.settingsChangelogContent { - display: flex; - flex-direction: column; - padding: 15px; -} - -/* Changelog header container. */ -.settingsChangelogHeadline { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid rgba(126, 126, 126, 0.57); -} -/* Changelog header label. */ -.settingsChangelogLabel { - font-size: 12px; - color: grey; - font-weight: bold; -} - -/* Changelog text content container. */ -.settingsChangelogText { - font-size: 12px; -} - -/* Styles for the changelog elements. */ -.settingsChangelogText p { - margin-bottom: 16px; - line-height: 1.5; -} -.settingsChangelogText blockquote { - border-left: 0.25em solid rgba(126, 126, 126, 0.95); - margin: 0px; - padding: 0 0 0 1em; - color: rgba(255, 255, 255, 0.85); -} -.settingsChangelogText code { - padding: 0.1em 0.4em; - font-size: 85%; - background-color: rgba(255, 255, 255, 0.25); - color: white; - border-radius: 3px; - font-family: 'Avenir Book'; -} -.settingsChangelogText li+li { - margin-top: .25em; -} -.settingsChangelogText a.commit-link { - font-weight: 400; - color: #ffffff; - text-decoration: none; -} -.settingsChangelogText a.commit-link:hover { - text-decoration: underline !important; - text-decoration-color: black; -} -.settingsChangelogText tt { - padding: 0.1em 0.4em; - font-size: 86%; - background-color: white; - border-radius: 3px; - color: black; - font-weight: bold; -} -.settingsChangelogText a.commit-link:hover tt { - text-decoration: underline; - text-decoration-color: black; -} -.settingsChangelogText .highlight { - background: rgba(0, 0, 0, 0.30); - user-select: initial; - padding: 5px 10px; -} -.settingsChangelogText .highlight pre { - margin: 0px; -} - -/* Container for the changelog button. */ -.settingsChangelogActions { - padding: 0px 15px 5px 15px; -} - -/* Open changelog on GitHub. */ -.settingsChangelogButton { - padding: 0px; -} - -/* * * -* Settings View (Updates Tab) -* * */ - -/* Main about content container. */ -#settingsUpdateStatusContainer { - display: flex; - flex-direction: column; - background: rgba(0, 0, 0, 0.25); - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - width: 75%; - margin-bottom: 20px; -} - -/* Update content. */ -#settingsUpdateStatusContent { - display: flex; - flex-direction: column; - padding: 15px; -} - -/* Update header elements. */ -#settingsUpdateStatusHeadline { - display: flex; - align-items: center; - padding-bottom: 5px; - border-bottom: 1px solid rgba(126, 126, 126, 0.57); -} -#settingsUpdateTitle { - font-size: 16px; - padding-left: 10px; - font-weight: bold; -} - -/* Update version container. */ -#settingsUpdateVersion { - display: flex; - align-items: center; - padding: 10px 0px; - border-bottom: 1px solid rgba(126, 126, 126, 0.57); -} - -/* Checkmark next to the version information. */ -#settingsUpdateVersionCheck { - border-radius: 50%; - background: #23aa23; - text-align: center; - font-weight: bold; - margin: 11px 12px; - color: white; - height: 15px; - width: 15px; - font-size: 12px; - line-height: 17px; -} - -/* Update version details container. */ -#settingsUpdateVersionDetails { - margin-left: 10px; -} - -/* Release type text. */ -#settingsUpdateVersionTitle { - font-size: 12px; - font-family: 'Avenir Medium'; - color: #23aa23; - font-weight: bold; -} - -/* Current version text. */ -#settingsUpdateVersionLine { - font-size: 10px; - color: grey; - font-weight: bold; -} - -/* Update action container. */ -#settingsUpdateActionContainer { - padding-top: 10px; - font-size: 14px; - font-weight: bold; -} - -/* Update action button styles. */ -#settingsUpdateActionButton { - display: flex; - flex-direction: column; - padding-left: 10px; - background: none; - border: none; - font-size: 14px; - font-weight: bold; - cursor: pointer; - outline: none; - text-align: left; - transition: 0.25s ease; -} -#settingsUpdateActionButton:hover, -#settingsUpdateActionButton:focus { - text-shadow: 0px 0px 20px white, 0px 0px 20px white, 0px 0px 20px white; -} -#settingsUpdateActionButton:active { - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; - color: #c7c7c7; -} -#settingsUpdateActionButton:disabled { - pointer-events: none; -} - -/******************************************************************************* - * * - * Landing View (Structural Styles) * - * * - ******************************************************************************/ - -/* Main content container. */ -#landingContainer { - height: 100%; - position: relative; - transition: background 2s ease; - overflow-y: hidden; -} - -/* Upper content container. */ -#landingContainer > #upper { - position: relative; - transition: top 2s ease; - top: 0px; - height: 77%; - display: flex; -} -#landingContainer > #upper > #left { - display: inline-flex; - width: 15%; - height: 100%; - justify-content: flex-end; -} -#landingContainer > #upper > #content { - display: inline-flex; - width: 70%; - height: 100%; -} -#landingContainer > #upper > #right { - display: inline-flex; - width: 15%; - height: 100%; -} - -/* Lower content container. */ -#landingContainer > #lower { - height: 23%; - display: flex; - background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0)); -} -#landingContainer > #lower > #left { - position: relative; - transition: top 2s ease; - top: 0px; - height: 100%; - width: 33%; - display: inline-flex; - justify-content: center; -} -#landingContainer > #lower > #left #content { - position: relative; - top: 25px; - display: inline-flex; - line-height: 24px; - left: 50px; -} -#landingContainer > #lower > #center { - position: relative; - transition: top 2s ease; - top: 0px; - height: 100%; - width: 34%; - display: inline-flex; - justify-content: center; -} -#landingContainer > #lower > #center #content { - position: relative; - z-index: 500; - transition: top 2s ease; - top: 10px; -} -#landingContainer > #lower > #right { - position: relative; - transition: top 2s ease; - top: 0px; - height: 100%; - width: 33%; - display: inline-flex; -} - -/******************************************************************************* - * * - * Landing View (News Styles) * - * * - ******************************************************************************/ - -/* Main container. */ -#newsContainer { - position: absolute; - top: 100%; - height: 100%; - width: 100%; - transition: top 2s ease; - display: flex; - align-items: flex-end; - justify-content: center; -} - -/* News content container. */ -#newsContent { - height: 82vh; - width: 100%; - display: flex; - -webkit-user-select: initial; - position: relative; -} - -/* Drop shadow displayed when content is scrolled out of view. */ -#newsContent:before { - content: ''; - background: linear-gradient(rgba(0, 0, 0, 0.25), transparent); - width: 100%; - height: 5px; - position: absolute; - opacity: 0; - transition: opacity 0.25s ease; -} -#newsContent[scrolled]:before { - opacity: 1; -} - -/* News article status container (left). */ -#newsStatusContainer { - width: calc(30% - 60px); - height: calc(100% - 30px); - padding: 15px 15px 15px 45px; - display: flex; - flex-direction: column; - justify-content: space-between; - position: relative; -} - -/* News status content. */ -#newsStatusContent { - display: flex; - flex-direction: column; - align-items: flex-end; -} - -/* News title wrapper. */ -#newsTitleContainer { - display: flex; - max-width: 90%; -} - -/* News article title styles. */ -#newsArticleTitle { - font-size: 18px; - font-weight: bold; - font-family: 'Avenir Medium'; - color: white; - text-decoration: none; - transition: 0.25s ease; - outline: none; - text-align: right; -} -#newsArticleTitle:hover, -#newsArticleTitle:focus { - text-shadow: 0px 0px 20px white; -} -#newsArticleTitle:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7; -} - -/* News meta container. */ -#newsMetaContainer { - display: flex; - flex-direction: column; -} - -/* Date and author wrappers. */ -#newsArticleDateWrapper, -#newsArticleAuthorWrapper { - display: flex; - justify-content: flex-end; -} - -/* Date and author shared styles. */ -#newsArticleDate, -#newsArticleAuthor { - display: inline-block; - font-size: 10px; - padding: 0px 5px; - font-weight: bold; - border-radius: 2px; -} - -/* Date styles. */ -#newsArticleDate { - background: white; - color: black; - margin-top: 5px; -} - -/* Author styles. */ -#newsArticleAuthor { - background: #a02d2a; -} - -/* News article comments styles. */ -#newsArticleComments { - margin-top: 5px; - display: inline-block; - font-size: 10px; - color: #ffffff; - text-decoration: none; - transition: 0.25s ease; - outline: none; - text-align: right; -} -#newsArticleComments:focus, -#newsArticleComments:hover { - color: #e0e0e0; -} -#newsArticleComments:active { - color: #c7c7c7; -} - -/* Article content container (right). */ -#newsArticleContainer { - width: calc(100% - 25px); - height: 100%; - margin: 0px 0px 0px 25px; -} - -/* Article content styles. */ -#newsArticleContentScrollable { - font-size: 12px; - overflow-y: scroll; - height: 100%; - padding: 0px 15px 0px 15px; -} -#newsArticleContentScrollable img, -#newsArticleContentScrollable iframe { - max-width: 95%; - display: block; - margin: 0 auto; -} -#newsArticleContentScrollable a { - color: rgba(202, 202, 202, 0.75); - transition: 0.25s ease; - outline: none; -} -#newsArticleContentScrollable a:hover, -#newsArticleContentScrollable a:focus { - color: rgba(255, 255, 255, 0.75); -} -#newsArticleContentScrollable a:active { - color: rgba(165, 165, 165, 0.75); -} -#newsArticleContentScrollable::-webkit-scrollbar { - width: 2px; -} -#newsArticleContentScrollable::-webkit-scrollbar-track { - display: none; -} -#newsArticleContentScrollable::-webkit-scrollbar-thumb { - border-radius: 10px; - box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.50); -} -.bbCodeSpoilerButton { - background: none; - border: none; - outline: none; - cursor: pointer; - font-size: 16px; - transition: 0.25s ease; - width: 100%; - border-bottom: 1px solid white; - padding-bottom: 15px; -} -.bbCodeSpoilerButton:hover, -.bbCodeSpoilerButton:focus { - text-shadow: 0px 0px 20px #ffffff, 0px 0px 20px #ffffff, 0px 0px 20px #ffffff; -} -.bbCodeSpoilerButton:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; -} -.bbCodeSpoilerText { - display: none; - padding: 15px 0px; - border-bottom: 1px solid white; -} - - -#newsArticleContentWrapper { - width: 80%; -} - -.newsArticleSpacerTop { - height: 15px; -} - -/* Div to add spacing at the end of a news article. */ -.newsArticleSpacerBot { - height: 30px; -} - -/* News navigation container. */ -#newsNavigationContainer { - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 10px; - -webkit-user-select: none; - position: absolute; - bottom: 15px; - right: 0px; -} - -/* Navigation status span. */ -#newsNavigationStatus { - font-size: 12px; - margin: 0px 15px; -} - -/* Left and right navigation button styles. */ -#newsNavigateLeft, -#newsNavigateRight { - background: none; - border: none; - outline: none; - height: 20px; - cursor: pointer; -} -#newsNavigateLeft:hover #newsNavigationLeftSVG, -#newsNavigateLeft:focus #newsNavigationLeftSVG, -#newsNavigateRight:hover #newsNavigationRightSVG, -#newsNavigateRight:focus #newsNavigationRightSVG { - -webkit-filter: drop-shadow(0px 0px 2px #fff); -} -#newsNavigateLeft:active #newsNavigationLeftSVG .arrowLine, -#newsNavigateRight:active #newsNavigationRightSVG .arrowLine { - stroke: #c7c7c7; -} -#newsNavigateLeft:active #newsNavigationLeftSVG, -#newsNavigateRight:active #newsNavigationRightSVG { - -webkit-filter: drop-shadow(0px 0px 2px #c7c7c7); -} -#newsNavigateLeft:disabled #newsNavigationLeftSVG .arrowLine, -#newsNavigateRight:disabled #newsNavigationRightSVG .arrowLine { - stroke: rgba(255, 255, 255, 0.75); -} -#newsNavigationLeftSVG { - transform: rotate(-90deg); - width: 15px; -} -#newsNavigationRightSVG { - transform: rotate(90deg); - width: 15px; -} - -/* News error (message) container. */ -#newsErrorContainer { - height: 100%; - display: flex; - align-items: center; - flex-direction: column; - justify-content: center; -} -#newsErrorFailed { - display: flex; - align-items: center; - flex-direction: column; - justify-content: center; -} - -/* News error content (message). */ -.newsErrorContent { - font-size: 20px; -} -#newsErrorLoading { - display: flex; - width: 168.92px; -} -#nELoadSpan { - white-space: pre; -} -/* News error retry button styles. */ -#newsErrorRetry { - font-size: 12px; - font-weight: bold; - cursor: pointer; - background: none; - border: none; - outline: none; - transition: 0.25s ease; -} -#newsErrorRetry:focus, -#newsErrorRetry:hover { - text-shadow: 0px 0px 20px white; -} -#newsErrorRetry:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7; -} - -/******************************************************************************* - * * - * Landing View (Top Styles) * - * * - ******************************************************************************/ - -/* * * -* Landing View (Top Styles) | Left Content -* * */ - -/* Logo image. */ -#image_seal { - height: 70px; - width: auto; - position: relative; - border: 2px solid white; - box-sizing: border-box; - border-radius: 50%; -} - -/* Logo container styles. */ -#image_seal_container { - position: relative; - height: 70px; - width: 70px; - border-radius: 50%; - margin-top: 50px; -} - -/* Logo container styles w/ update. */ -#image_seal_container[update]{ - cursor: pointer -} -#image_seal_container[update]:before, -#image_seal_container[update]:after { - cursor: pointer; - position: absolute; - content: ''; - height: 100%; - width: 100%; - top: 0%; - left: 0%; - border-radius: 50%; - box-shadow: 0 0 15px #43c628; - animation: glow-grow 4s ease-out infinite; - background: rgba(0, 0, 0, 0.15); -} -#image_seal_container[update]:before { - animation-delay: 2s; -} - -/* Update available tooltip styles. */ -#updateAvailableTooltip { - cursor: pointer; - visibility: hidden; - opacity: 0; - width: 100px; - height: 15px; - background-color: rgb(0, 0, 0); - color: #fff; - text-align: center; - border-radius: 4px; - padding: 2px; - position: absolute; - z-index: 1; - top: 115%; - left: -17.5px; - font-family: 'Avenir Medium'; - font-size: 12px; - transition: visibility 0s linear 0.25s, opacity 0.25s ease; -} -#updateAvailableTooltip::after { - content: " "; - position: absolute; - left: 50%; - bottom: 100%; - margin-left: -5px; - border-width: 5px; - border-style: solid; - border-color: transparent transparent rgb(0, 0, 0) transparent; -} -#image_seal_container[update]:hover #updateAvailableTooltip { - visibility: visible; - opacity: 1; - transition-delay: 0s; -} - -/* Update available animation. */ -@keyframes glow-grow { - 0% { - opacity: 0; - transform: scale(1); - } - 80% { - opacity: 1; - } - 100% { - transform: scale(1.5); - opacity: 0; - } -} - -/* * * -* Landing View (Bottom Styles) | Right Content -* * */ - -/* Wrapper container for top, right content. */ -#rightContainer { - display: flex; - flex-direction: column; - position: relative; - top: 50px; - align-items: flex-start; - height: calc(100% - 50px); -} - -/* Right hand user content container. */ -#user_content { - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - position: relative; -} - -/* User profile avatar container. */ -#avatarContainer { - border-radius: 50%; - border: 2px solid #cad7e1; - box-sizing: border-box; - background: rgba(1, 2, 1, 0.5); - height: 70px; - width: 70px; - box-shadow: 0px 0px 10px 0px rgb(0, 0, 0); - overflow: hidden; - position: relative; - background-position: center; - background-repeat: no-repeat; - background-size: contain; -} - -/* Avatar edit overlay. */ -#avatarOverlay { - opacity: 0; - position: absolute; - z-index: 1; - display: flex; - justify-content: center; - align-items: center; - transition: 0.25s ease; - font-weight: bold; - letter-spacing: 2px; - background-color: rgba(0, 0, 0, 0.35); - -webkit-user-select: none; - border: none; - cursor: pointer; - width: 100%; - height: 100%; - border-radius: 50%; -} -#avatarOverlay:hover, -#avatarOverlay:focus { - opacity: 1; -} -#avatarOverlay:active { - background-color: rgba(0, 0, 0, 0.45); -} - -/* User profile name text. */ -#user_text { - font-size: 12px; - min-width: 135px; - font-weight: 900; - letter-spacing: 1px; - text-shadow: 0px 0px 20px black; - position: absolute; - right: 95px; - text-align: right; - -webkit-user-select: initial; -} - -/* Social media icon content container. */ -#mediaContent { - position: relative; - display: flex; - flex-direction: column; - margin-top: 25px; - height: calc(100% - 95px); - width: 70px; - align-items: center; -} - -/* Social Media Icon division containers. */ -#internalMedia, #externalMedia { - display: flex; - flex-direction: column; -} - -/* Container object which wraps an icon to ensure fluid transitions. */ -.mediaContainer { - display: flex; - justify-content: center; - align-items: center; - height: 27px; -} - -/* Divider bar between the external and internal icons. */ -.mediaDivider { - height: 1px; - width: 14px; - background: rgb(255, 255, 255); - margin: 10px 0px; -} - -/* Social media icon shared styles. */ -.mediaSVG { - fill: #ffffff; - height: 12px; - transition: 0.25s ease; - cursor: pointer; - height: 12px; - width: 25px; -} -.mediaSVG:hover, -.mediaURL:focus .mediaSVG, -.mediaSVG:active { - height: 20px; -} - -/* Social media URL shared styles. */ -.mediaURL { - outline: none; -} - -/* Internal media button shared styles. */ -.mediaButton { - background: none; - border: none; - padding: 0px; - display: flex; - align-items: center; - outline: none; -} - -#settingsMediaContainer { - position: relative; -} - -/* Settings icon colors. */ -#settingsSVG { - stroke: #ffffff; - height: 15px; -} -.mediaButton:hover #settingsSVG, -.mediaButton:focus #settingsSVG, -.mediaButton:active #settingsSVG { - height: 23px; -} - -/* Settings tooltip styles. */ -#settingsTooltip { - visibility: hidden; - opacity: 0; - width: 75px; - height: 20px; - background-color: rgba(0, 0, 0, 0.75); - text-align: center; - border-radius: 4px; - position: absolute; - z-index: 1; - right: 130%; - font-size: 12px; - line-height: 20px; - transition: visibility 0s linear 0.25s, opacity 0.25s ease; -} -#settingsTooltip::after { - content: " "; - position: absolute; - top: 50%; - left: 100%; - margin-top: -5px; - border-width: 5px; - border-style: solid; - border-color: transparent transparent transparent rgba(0, 0, 0, 0.75); -} -.mediaButton:hover #settingsTooltip, -.mediaButton:focus #settingsTooltip, -.mediaButton:active #settingsTooltip { - visibility: visible; - opacity: 1; - transition-delay:0s; -} - -/* Twitter icon colors. */ -#twitterSVG:hover, -#twitterURL:focus #twitterSVG { - fill: #1da1f2; -} -#twitterSVG:active { - fill: #1b8dd4; -} - -/* Instagram icon colors. */ -#instagramSVG:hover, -#instagramURL:focus #instagramSVG { - fill: url('#instaFill') - /*fill: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285AEB 90%); */ -} -#instagramSVG:active { - fill: url('#instaFill') -} - -/* Youtube icon colors. */ -#youtubeSVG:hover, -#youtubeURL:focus #youtubeSVG { - fill: #f00; -} -#youtubeSVG:active { - fill: #ea0202; -} - -/* Discord icon colors. */ -#discordSVG:hover, -#discordURL:focus #discordSVG { - fill: #7288d9; -} -#discordSVG:active { - fill: #657ac4; -} - -/******************************************************************************* - * * - * Landing View (Bottom Styles) * - * * - ******************************************************************************/ - -/* Style for a general label on the bottom of the landing view. */ -.bot_label { - font-size: 9px; - letter-spacing: 1px; - font-weight: bold; - text-shadow: 0px 0px 0px #bebcbb; -} - -/* Divider used on the bottom of the landing view. */ -.bot_divider { - height: 25px; - width: 2px; - background: rgba(107, 105, 105, 0.7); - margin-left: 20px; - margin-right: 20px; -} - -/* * * -* Landing View (Bottom Styles) | Left Content -* * */ - -/* Maintains maximum width on the status bar. */ -#server_status_wrapper { - display: inline-flex; - width: 75px; -} - -/* Span which displays the player count of the selected server. */ -#player_count { - color: #949494; - font-size: 8px; - font-weight: 900; - text-shadow: 0px 0px 20px #949494; - margin-left: 10px; -} - -/* Wrapper container for the mojang status bar. */ -#mojangStatusWrapper { - position: relative; - display: flex; - cursor: pointer; -} - -/* Icon which displays the status of the mojang services. */ -#mojang_status_icon { - font-size: 30px; - color: #848484; - margin-left: 15px; - font-family: 'sans-serif'; -} - -/* Tooltip which displays more details about the mojang statuses. */ -#mojangStatusTooltip { - position: absolute; - visibility: hidden; - opacity: 0; - width: 145px; - min-height: 150px; - background-color: rgba(0, 0, 0, 0.75); - color: #fff; - border-radius: 4px; - padding: 5px 10px; - z-index: 1; - font-family: 'Avenir Medium'; - font-size: 12px; - transition: visibility 0s linear 0.25s, opacity 0.25s ease; - bottom: calc(100% + 15px); - transform: translateX(-50%); - margin-left: 50%; - box-shadow: 0px 0px 20px rgb(0, 0, 0); - cursor: default; -} -#mojangStatusTooltip:after { - content: " "; - position: absolute; - left: 50%; - top: 100%; - margin-left: -5px; - border-width: 5px; - border-style: solid; - border-color: rgba(0, 0, 0, 0.75) transparent transparent transparent; -} -#mojangStatusWrapper:hover #mojangStatusTooltip { - visibility: visible; - opacity: 1; - transition-delay: 0s; -} - -/* Tooltip title for the mojang statuses. */ -#mojangStatusTooltipTitle { - width: 100%; - text-align: center; - margin-bottom: 5px; - letter-spacing: 1px; -} - -/* Wrapper container for the non essential services title. */ -#mojangStatusNEContainer { - display: flex; - align-items: center; - margin: 10px 0px; -} - -/* White bar which surrounds the non essential service title. */ -.mojangStatusNEBar { - height: 1px; - width: 100%; - background: white; -} - -/* Non essential service title text. */ -#mojangStatusNETitle { - font-size: 10px; - padding: 0px 3px; - text-align: center; - letter-spacing: 1px; -} - -/* Wrapper container for mojang service information. */ -.mojangStatusContainer { - display: flex; -} - -/* Displays the name of the mojang service. */ -.mojangStatusName { - width: 100%; - font-size: 10px; - letter-spacing: 1px; - line-height: 12px; - padding: 6px 0px; -} - -/* Displays the status of the mojang service. */ -.mojangStatusIcon { - margin-right: 10px; - font-size: 18.5px; - color: #848484; -} - -/* * * -* Landing View (Bottom Styles) | Center Content -* * */ - -/* Button which opens the news view. */ -#newsButton { - background: none; - border: none; - cursor: pointer; - outline: none; -} -#newsButton:hover #newsButtonText, -#newsButton:focus #newsButtonText { - text-shadow: 0px 0px 20px #fff, 0px 0px 20px #fff; -} -#newsButton:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; -} - -#newsButton:hover #newsButtonSVG, -#newsButton:focus #newsButtonSVG { - -webkit-filter: drop-shadow(0px 0px 2px #fff); -} -#newsButton:active #newsButtonSVG .arrowLine { - stroke: #c7c7c7; -} -#newsButton:active #newsButtonSVG { - -webkit-filter: drop-shadow(0px 0px 2px #c7c7c7); -} -#newsButton:disabled #newsButtonSVG .arrowLine { - stroke: rgba(255, 255, 255, 0.75); -} - -/* Icon which indicates there is new news. */ -#newsButtonAlert { - width: 5px; - height: 5px; - position: absolute; - border-radius: 50%; - background: red; - right: -1px; - top: 50%; -} - -/* Arrow image which floats above the news button. */ -#newsButtonSVG { - height: 11px; - margin-left: -2px; - transition: 0.25s ease; -} - -/* Span which contains the news button text. */ -#newsButtonText { - color: white; - font-weight: 900; - letter-spacing: 2px; - text-shadow: 0px 0px 0px #bebcbb; - font-size: 11px; - line-height: 30px; - display: flex; - transition: 0.25s ease; -} - -/* * * -* Landing View (Bottom Styles) | Right Content -* * */ - -/* Main launch content container. */ -#landingContainer > #lower > #right #launch_content { - position: relative; - top: 25px; - display: inline-flex; -} - -/* The launch button. */ -#launch_button { - background: none; - border: none; - cursor: pointer; - font-weight: 900; - letter-spacing: 2px; - text-shadow: 0px 0px 0px #bebcbb; - font-size: 20px; - padding: 0px; - transition: 0.25s ease; - outline: none; -} -#launch_button:hover, -#launch_button:focus { - text-shadow: 0px 0px 20px #fff, 0px 0px 20px #fff; -} -#launch_button:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; -} -#launch_button:disabled { - color: #c7c7c7; - cursor: default; - pointer-events: none; -} - -/* Launch details main container, hidden until launch processing begins. */ -#launch_details { - position: relative; - top: 25px; - display: none; -} - -/* Left side of launch details container, displays percentage and a divider. */ -#launch_details_left { - display: flex; -} - -/* Span which displays percentage complete. */ -#launch_progress_label { - font-weight: 900; - letter-spacing: 1px; - text-shadow: 0px 0px 0px #bebcbb; - font-size: 20px; - min-width: 53.21px; - max-width: 53.21px; - text-align: right; -} - -/* Right side of launch details container, displays progress bar and details. */ -#launch_details_right { - display: flex; - flex-direction: column; - justify-content: center; -} - -/* Button which opens the server selection view. */ -#server_selection_button { - background: none; - border: none; - outline: none; - cursor: pointer; - line-height: 24px; - padding: 0px; - transition: 0.25s ease; -} -#server_selection_button:hover, -#server_selection_button:focus { - text-shadow: 0px 0px 20px #fff, 0px 0px 20px #fff, 0px 0px 20px #fff; -} -#server_selection_button:active { - color: #c7c7c7; - text-shadow: 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7, 0px 0px 20px #c7c7c7; -} - -/* Progress bar styles. */ -#launch_progress[value] { - height: 3px; - width: 265px; - -webkit-appearance: none; -} -#launch_progress[value]::-webkit-progress-bar { - background-color: transparent; -} -#launch_progress[value]::-webkit-progress-value { - background-color: #fff; -} - -/* Span which displays information about the status of the launch process. */ -#launch_details_text { - font-size: 11px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -/******************************************************************************* - * * - * Overlay View (overlay.ejs) * - * * - ******************************************************************************/ - -/* * * -* Overlay View (Main Content) -* * */ - -/* Overlay container, placed over the main div. */ -#overlayContainer { - position: absolute; - z-index: 500; - top: 22px; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: calc(100% - 22px); - background: rgba(0, 0, 0, 0.50); -} - -/* Main overlay content. */ -#overlayContent { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - /*justify-content: space-between;*/ - width: 300px; - /*height: 35%;*/ - box-sizing: border-box; - padding: 15px 0px; - /* background-color: #424242; */ - text-align: center; -} - -/* Main overlay content anchor styles. */ -#overlayContent a, -#overlayDismiss { - color: rgba(202, 202, 202, 0.75); - transition: 0.25s ease; -} -#overlayContent a:hover, -#overlayContent a:focus, -#overlayDismiss:focus { - color: rgba(255, 255, 255, 0.75); -} -#overlayContent a:active, -#overlayDismiss:active { - color: rgba(165, 165, 165, 0.75); -} - -/* Add spacing between overlay content elements. */ -#overlayContent > *:first-child { - margin-top: 0px !important; -} -#overlayContent > *:last-child { - margin-bottom: 0px !important; -} -#overlayContent > * { - margin: 8px 0px; -} - -/* Overlay title styles. */ -#overlayTitle { - font-family: 'Avenir Medium'; - font-size: 20px; - font-weight: bold; - letter-spacing: 1px; - -webkit-user-select: initial; -} - -/* Overlay description styles. */ -#overlayDesc { - font-size: 12px; - font-weight: bold; - -webkit-user-select: initial; -} - -/* Div which contains action buttons. */ -#overlayActionContainer { - display: flex; - flex-direction: column; - justify-content: center; -} - -/* Overlay acknowledge button styles. */ -#overlayAcknowledge { - background: none; - border: 1px solid #ffffff; - color: white; - font-family: 'Avenir Medium'; - font-weight: bold; - border-radius: 2px; - padding: 0px 8.1px; - cursor: pointer; - transition: 0.25s ease; -} -#overlayAcknowledge:hover, -#overlayAcknowledge:focus { - box-shadow: 0px 0px 10px 0px #fff; - outline: none; -} -#overlayAcknowledge:active { - border-color: rgba(255, 255, 255, 0.75); - color: rgba(255, 255, 255, 0.75); -} - -/* Overlay dismiss option styles. */ -#overlayDismiss { - font-weight: bold; - font-size: 10px; - text-decoration: none; - padding-top: 2.5px; - background: none; - border: none; - outline: none; - cursor: pointer; -} -#overlayDismiss:hover { - color: rgba(255, 255, 255, 0.75); -} -#overlayDismiss:active { - color: rgba(165, 165, 165, 0.75); -} - -/* * * -* Overlay View (Server + Account Selection Content) -* * */ - -/* Server selection content container. */ -#serverSelectContent, -#accountSelectContent { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 75%; -} - -/* Server selection header. */ -#serverSelectHeader, -#accountSelectHeader { - font-family: 'Avenir Medium'; - font-size: 20px; - font-weight: bold; - color: #fff; - margin-bottom: 25px; -} - -/* Wrapper div for the list of available servers. */ -#serverSelectList, -#accountSelectList { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - max-height: 65%; - min-height: 40%; -} - -/* Scrollable div which lists the available servers. */ -#serverSelectListScrollable, -#accountSelectListScrollable { - padding: 0px 5px; - overflow-y: scroll; -} -#serverSelectListScrollable::-webkit-scrollbar, -#accountSelectListScrollable::-webkit-scrollbar { - width: 2px; -} -#serverSelectListScrollable::-webkit-scrollbar-track, -#accountSelectListScrollable::-webkit-scrollbar-track { - display: none; -} -#serverSelectListScrollable::-webkit-scrollbar-thumb, -#accountSelectListScrollable::-webkit-scrollbar-thumb { - border-radius: 10px; - box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.50); -} - -/* Content container for a server listing. */ -.serverListing { - border: none; - padding: 0px; - width: 375px; - min-height: 60px; - display: flex; - justify-content: flex-start; - align-items: center; - opacity: 0.6; - transition: 0.25s ease; - cursor: pointer; - position: relative; - background: rgba(131, 131, 131, 0.25); -} -.serverListing[selected] { - cursor: default; - opacity: 1.0; -} -.serverListing:hover, -.serverListing:focus { - outline: none; - opacity: 1.0; -} - -.accountListing { - color: white; - border: 1px solid rgba(126, 126, 126, 0.57); - border-radius: 3px; - padding: 5px 45px; - width: 250px; - display: flex; - justify-content: flex-start; - align-items: center; - opacity: 0.6; - transition: 0.25s ease; - cursor: pointer; - position: relative; - background: rgba(0, 0, 0, 0.25); -} -.accountListing[selected] { - cursor: default; - opacity: 1.0; -} -.accountListing:hover, -.accountListing:focus { - outline: none; - opacity: 1.0; -} - -.accountListingName { - display: flex; - height: 100%; - width: 100%; - padding-left: 10px; -} - -/* Add spacing between server listings. */ -#serverSelectListScrollable > .serverListing:not(:first-child):not(:last-child), -#accountSelectListScrollable > .accountListing:not(:first-child):not(:last-child) { - margin: 5px 0px; -} -#serverSelectListScrollable > .serverListing:first-child, -#accountSelectListScrollable > .accountListing:first-child { - margin-bottom: 5px; -} -#serverSelectListScrollable > .serverListing:last-child, -#accountSelectListScrollable > .accountListing:last-child { - margin-top: 5px; -} - -/* Server listing image. */ -.serverListingImg { - margin: 0px 10px 0px 5px; - border: 1px solid #fff; - height: 50px; - width: 50px; -} - -/* Content container for the server listing's details. */ -.serverListingDetails { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: space-between; - height: 50px; -} - -/* The name of the server listing. */ -.serverListingName { - font-size: 14px; - font-weight: bold; -} - -/* Description for the server listing. */ -.serverListingDescription { - font-size: 10px; - line-height: 10px; - font-weight: bold; -} - -/* Content container for the server listing's information. */ -.serverListingInfo { - width: 100%; - display: flex; - justify-content: flex-start; -} - -/* The minecraft version of the server listing. */ -.serverListingVersion { - font-size: 10px; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - line-height: 12px; - height: 12px; - border-radius: 2px; - background: rgba(31, 140, 11, 0.8); - padding: 0px 2px; -} - -/* The revision version of the server's manifest. */ -.serverListingRevision { - color: #969696; - font-size: 10px; - line-height: 12px; - padding: 0px 5px; -} - -/* Star which indicates the default (main) server. */ -.serverListingStarWrapper { - display: flex; - align-items: center; - cursor: pointer; - height: 12px; - position: relative; -} -/* Tooltip which displays when hovering over the star. */ -.serverListingStarTooltip { - visibility: hidden; - opacity: 0; - width: 65px; - background-color: rgba(0, 0, 0, 0.40); - text-align: center; - border-radius: 4px; - position: absolute; - z-index: 1; - left: 130%; - font-size: 10px; - transition: visibility 0s linear 0.25s, opacity 0.25s ease; -} -.serverListingStarTooltip::after { - content: " "; - position: absolute; - top: 50%; - right: 100%; /* To the left of the tooltip */ - margin-top: -5px; - border-width: 5px; - border-style: solid; - border-color: transparent rgba(0, 0, 0, 0.40) transparent transparent; -} -.serverListingStarWrapper:hover .serverListingStarTooltip { - visibility: visible; - opacity: 1; - transition-delay:0s; -} - -/* Content container which contains the server select actions. */ -#serverSelectActions, -#accountSelectActions { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin-top: 25px; -} - -/* Server selection confirm button styles. */ -#serverSelectConfirm, -#accountSelectConfirm { - background: none; - border: 1px solid #ffffff; - color: white; - font-family: 'Avenir Medium'; - font-weight: bold; - border-radius: 2px; - padding: 0px 8.1px; - cursor: pointer; - transition: 0.25s ease; - min-height: 20.67px; -} -#serverSelectConfirm:hover, -#serverSelectConfirm:focus, -#accountSelectConfirm:hover, -#accountSelectConfirm:focus { - box-shadow: 0px 0px 10px 0px #fff; - outline: none; -} -#serverSelectConfirm:active, -#accountSelectConfirm:active { - border-color: rgba(255, 255, 255, 0.75); - color: rgba(255, 255, 255, 0.75); -} - -/* Server selection cancel button styles. */ -#serverSelectCancel, -#accountSelectCancel { - font-weight: bold; - font-size: 10px; - text-decoration: none; - padding-top: 2.5px; - color: rgba(202, 202, 202, 0.75); - transition: 0.25s ease; - background: none; - border: none; - outline: none; - cursor: pointer; -} -#serverSelectCancel:hover, -#serverSelectCancel:focus, -#accountSelectCancel:hover, -#accountSelectCancel:focus { - color: rgba(255, 255, 255, 0.75); -} -#serverSelectCancel:active, -#accountSelectCancel:active { - color: rgba(165, 165, 165, 0.75); -} - -/******************************************************************************* - * * - * Loading Element (app.ejs) * - * * - ******************************************************************************/ - -/* Loading container, placed above everything. */ -#loadingContainer { - position: absolute; - z-index: 400; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: calc(100% - 22px); -} - -/* Loading content container. */ -#loadingContent { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -/* Spinner container. */ -#loadSpinnerContainer { - position: relative; - display: flex; - align-items: center; - justify-content: center; -} - -/* Stationary image for the spinner. */ -#loadCenterImage { - position: absolute; - width: 277px; - height: auto; -} - -/* Rotating image for the spinner. */ -#loadSpinnerImage { - width: 280px; - height: auto; - z-index: 400; -} - -/* Rotating animation for the spinner. */ -@keyframes rotating { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -/* Class which is applied when the spinner image is spinning. */ -.rotating { - animation: rotating 10s linear infinite; -} \ No newline at end of file diff --git a/app/assets/fonts/Avenir-Black.ttf b/app/assets/fonts/Avenir-Black.ttf deleted file mode 100644 index 9da57525..00000000 Binary files a/app/assets/fonts/Avenir-Black.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-BlackOblique.ttf b/app/assets/fonts/Avenir-BlackOblique.ttf deleted file mode 100644 index 1b73af97..00000000 Binary files a/app/assets/fonts/Avenir-BlackOblique.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-Book.ttf b/app/assets/fonts/Avenir-Book.ttf deleted file mode 100644 index de9c227f..00000000 Binary files a/app/assets/fonts/Avenir-Book.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-BookOblique.ttf b/app/assets/fonts/Avenir-BookOblique.ttf deleted file mode 100644 index 4258fda1..00000000 Binary files a/app/assets/fonts/Avenir-BookOblique.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-Heavy.ttf b/app/assets/fonts/Avenir-Heavy.ttf deleted file mode 100644 index 67738068..00000000 Binary files a/app/assets/fonts/Avenir-Heavy.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-HeavyOblique.ttf b/app/assets/fonts/Avenir-HeavyOblique.ttf deleted file mode 100644 index d3588cd1..00000000 Binary files a/app/assets/fonts/Avenir-HeavyOblique.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-Light.ttf b/app/assets/fonts/Avenir-Light.ttf deleted file mode 100644 index 69761e4b..00000000 Binary files a/app/assets/fonts/Avenir-Light.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-LightOblique.ttf b/app/assets/fonts/Avenir-LightOblique.ttf deleted file mode 100644 index 6e1af66f..00000000 Binary files a/app/assets/fonts/Avenir-LightOblique.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-Medium.ttf b/app/assets/fonts/Avenir-Medium.ttf deleted file mode 100644 index 09770bba..00000000 Binary files a/app/assets/fonts/Avenir-Medium.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-MediumOblique.ttf b/app/assets/fonts/Avenir-MediumOblique.ttf deleted file mode 100644 index 0a68d909..00000000 Binary files a/app/assets/fonts/Avenir-MediumOblique.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-Oblique.ttf b/app/assets/fonts/Avenir-Oblique.ttf deleted file mode 100644 index bc02bfa7..00000000 Binary files a/app/assets/fonts/Avenir-Oblique.ttf and /dev/null differ diff --git a/app/assets/fonts/Avenir-Roman.ttf b/app/assets/fonts/Avenir-Roman.ttf deleted file mode 100644 index 295a5525..00000000 Binary files a/app/assets/fonts/Avenir-Roman.ttf and /dev/null differ diff --git a/app/assets/fonts/ringbearer.ttf b/app/assets/fonts/ringbearer.ttf deleted file mode 100644 index 5fb3a09b..00000000 Binary files a/app/assets/fonts/ringbearer.ttf and /dev/null differ diff --git a/app/assets/images/LoadingSeal.png b/app/assets/images/LoadingSeal.png deleted file mode 100644 index afe2c4d4..00000000 Binary files a/app/assets/images/LoadingSeal.png and /dev/null differ diff --git a/app/assets/images/LoadingText.png b/app/assets/images/LoadingText.png deleted file mode 100644 index 5e55b332..00000000 Binary files a/app/assets/images/LoadingText.png and /dev/null differ diff --git a/app/assets/images/SealCircle.ico b/app/assets/images/SealCircle.ico deleted file mode 100644 index e37b95df..00000000 Binary files a/app/assets/images/SealCircle.ico and /dev/null differ diff --git a/app/assets/images/SealCircle.png b/app/assets/images/SealCircle.png deleted file mode 100644 index 9e21d08b..00000000 Binary files a/app/assets/images/SealCircle.png and /dev/null differ diff --git a/app/assets/images/backgrounds/0.jpg b/app/assets/images/backgrounds/0.jpg deleted file mode 100644 index 66a2c12a..00000000 Binary files a/app/assets/images/backgrounds/0.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/1.jpg b/app/assets/images/backgrounds/1.jpg deleted file mode 100644 index e343cbe2..00000000 Binary files a/app/assets/images/backgrounds/1.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/2.jpg b/app/assets/images/backgrounds/2.jpg deleted file mode 100644 index 7a13e771..00000000 Binary files a/app/assets/images/backgrounds/2.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/3.jpg b/app/assets/images/backgrounds/3.jpg deleted file mode 100644 index 5c7be569..00000000 Binary files a/app/assets/images/backgrounds/3.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/4.jpg b/app/assets/images/backgrounds/4.jpg deleted file mode 100644 index 1db7800e..00000000 Binary files a/app/assets/images/backgrounds/4.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/5.jpg b/app/assets/images/backgrounds/5.jpg deleted file mode 100644 index beb4b148..00000000 Binary files a/app/assets/images/backgrounds/5.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/6.jpg b/app/assets/images/backgrounds/6.jpg deleted file mode 100644 index acdaa73a..00000000 Binary files a/app/assets/images/backgrounds/6.jpg and /dev/null differ diff --git a/app/assets/images/backgrounds/7.jpg b/app/assets/images/backgrounds/7.jpg deleted file mode 100644 index cc48f72f..00000000 Binary files a/app/assets/images/backgrounds/7.jpg and /dev/null differ diff --git a/app/assets/images/icons/arrow.svg b/app/assets/images/icons/arrow.svg deleted file mode 100644 index 93326051..00000000 --- a/app/assets/images/icons/arrow.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - arrow - - \ No newline at end of file diff --git a/app/assets/images/icons/discord.svg b/app/assets/images/icons/discord.svg deleted file mode 100644 index 87271281..00000000 --- a/app/assets/images/icons/discord.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - discord - - - - \ No newline at end of file diff --git a/app/assets/images/icons/instagram.svg b/app/assets/images/icons/instagram.svg deleted file mode 100644 index 00c70bda..00000000 --- a/app/assets/images/icons/instagram.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/link.svg b/app/assets/images/icons/link.svg deleted file mode 100644 index df151d4a..00000000 --- a/app/assets/images/icons/link.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - link - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/lock.svg b/app/assets/images/icons/lock.svg deleted file mode 100644 index 47a5343e..00000000 --- a/app/assets/images/icons/lock.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - Lock - - - - \ No newline at end of file diff --git a/app/assets/images/icons/microsoft.svg b/app/assets/images/icons/microsoft.svg deleted file mode 100644 index 78a4ed94..00000000 --- a/app/assets/images/icons/microsoft.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/mojang.svg b/app/assets/images/icons/mojang.svg deleted file mode 100644 index e1116b41..00000000 --- a/app/assets/images/icons/mojang.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/news.svg b/app/assets/images/icons/news.svg deleted file mode 100644 index 775578d4..00000000 --- a/app/assets/images/icons/news.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - News - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/profile.svg b/app/assets/images/icons/profile.svg deleted file mode 100644 index 6526c65f..00000000 --- a/app/assets/images/icons/profile.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - Profile - - - - \ No newline at end of file diff --git a/app/assets/images/icons/settings.svg b/app/assets/images/icons/settings.svg deleted file mode 100644 index 1a0ec766..00000000 --- a/app/assets/images/icons/settings.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - settings - - - - \ No newline at end of file diff --git a/app/assets/images/icons/sevenstar.svg b/app/assets/images/icons/sevenstar.svg deleted file mode 100644 index 4f55ef40..00000000 --- a/app/assets/images/icons/sevenstar.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Seven Pointed Star - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/sevenstar_circle.svg b/app/assets/images/icons/sevenstar_circle.svg deleted file mode 100644 index 9e8c8a8f..00000000 --- a/app/assets/images/icons/sevenstar_circle.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Seven Pointed Star with Circle - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/sevenstar_circle_extended.svg b/app/assets/images/icons/sevenstar_circle_extended.svg deleted file mode 100644 index 8651baa3..00000000 --- a/app/assets/images/icons/sevenstar_circle_extended.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - Seven Pointed Star Extended with Circle - - - \ No newline at end of file diff --git a/app/assets/images/icons/sevenstar_circle_hole.svg b/app/assets/images/icons/sevenstar_circle_hole.svg deleted file mode 100644 index 65250d4a..00000000 --- a/app/assets/images/icons/sevenstar_circle_hole.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - Seven Pointed Star with Circle and Hole - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/images/icons/sevenstar_circle_hole_extended.svg b/app/assets/images/icons/sevenstar_circle_hole_extended.svg deleted file mode 100644 index e549b4d4..00000000 --- a/app/assets/images/icons/sevenstar_circle_hole_extended.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - Seven Pointed Star Extended with Circle and Hole - - - - \ No newline at end of file diff --git a/app/assets/images/icons/sevenstar_extended.svg b/app/assets/images/icons/sevenstar_extended.svg deleted file mode 100644 index b8e28241..00000000 --- a/app/assets/images/icons/sevenstar_extended.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - Seven Pointed Star Extended - - \ No newline at end of file diff --git a/app/assets/images/icons/twitter.svg b/app/assets/images/icons/twitter.svg deleted file mode 100644 index bad6d42c..00000000 --- a/app/assets/images/icons/twitter.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - twitter> - - \ No newline at end of file diff --git a/app/assets/images/icons/youtube.svg b/app/assets/images/icons/youtube.svg deleted file mode 100644 index 38a2e8ba..00000000 --- a/app/assets/images/icons/youtube.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - youtube - - - - \ No newline at end of file diff --git a/app/assets/images/minecraft.icns b/app/assets/images/minecraft.icns deleted file mode 100644 index 60ce69d7..00000000 Binary files a/app/assets/images/minecraft.icns and /dev/null differ diff --git a/app/assets/js/assetexec.js b/app/assets/js/assetexec.js deleted file mode 100644 index f1af291b..00000000 --- a/app/assets/js/assetexec.js +++ /dev/null @@ -1,74 +0,0 @@ -let target = require('./assetguard')[process.argv[2]] -if(target == null){ - process.send({context: 'error', data: null, error: 'Invalid class name'}) - console.error('Invalid class name passed to argv[2], cannot continue.') - process.exit(1) -} -let tracker = new target(...(process.argv.splice(3))) - -const { LoggerUtil } = require('helios-core') -const logger = LoggerUtil.getLogger('AssetExec') - -//const tracker = new AssetGuard(process.argv[2], process.argv[3]) -logger.info('AssetExec Started') - -// Temporary for debug purposes. -process.on('unhandledRejection', r => console.log(r)) - -let percent = 0 -function assignListeners(){ - tracker.on('validate', (data) => { - process.send({context: 'validate', data}) - }) - tracker.on('progress', (data, acc, total) => { - const currPercent = parseInt((acc/total) * 100) - if (currPercent !== percent) { - percent = currPercent - process.send({context: 'progress', data, value: acc, total, percent}) - } - }) - tracker.on('complete', (data, ...args) => { - process.send({context: 'complete', data, args}) - }) - tracker.on('error', (data, error) => { - process.send({context: 'error', data, error}) - }) -} - -assignListeners() - -process.on('message', (msg) => { - if(msg.task === 'execute'){ - const func = msg.function - let nS = tracker[func] // Nonstatic context - let iS = target[func] // Static context - if(typeof nS === 'function' || typeof iS === 'function'){ - const f = typeof nS === 'function' ? nS : iS - const res = f.apply(f === nS ? tracker : null, msg.argsArr) - if(res instanceof Promise){ - res.then((v) => { - process.send({result: v, context: func}) - }).catch((err) => { - process.send({result: err.message || err, context: func}) - }) - } else { - process.send({result: res, context: func}) - } - } else { - process.send({context: 'error', data: null, error: `Function ${func} not found on ${process.argv[2]}`}) - } - } else if(msg.task === 'changeContext'){ - target = require('./assetguard')[msg.class] - if(target == null){ - process.send({context: 'error', data: null, error: `Invalid class ${msg.class}`}) - } else { - tracker = new target(...(msg.args)) - assignListeners() - } - } -}) - -process.on('disconnect', () => { - logger.info('AssetExec Disconnected') - process.exit(0) -}) \ No newline at end of file diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js deleted file mode 100644 index d447854a..00000000 --- a/app/assets/js/assetguard.js +++ /dev/null @@ -1,1911 +0,0 @@ -// Requirements -const AdmZip = require('adm-zip') -const async = require('async') -const child_process = require('child_process') -const crypto = require('crypto') -const EventEmitter = require('events') -const fs = require('fs-extra') -const { LoggerUtil } = require('helios-core') -const nodeDiskInfo = require('node-disk-info') -const StreamZip = require('node-stream-zip') -const path = require('path') -const Registry = require('winreg') -const request = require('request') -const tar = require('tar-fs') -const zlib = require('zlib') - -const ConfigManager = require('./configmanager') -const DistroManager = require('./distromanager') -const isDev = require('./isdev') - -const isARM64 = process.arch === 'arm64' - -// Classes - -/** Class representing a base asset. */ -class Asset { - /** - * Create an asset. - * - * @param {any} id The id of the asset. - * @param {string} hash The hash value of the asset. - * @param {number} size The size in bytes of the asset. - * @param {string} from The url where the asset can be found. - * @param {string} to The absolute local file path of the asset. - */ - constructor(id, hash, size, from, to){ - this.id = id - this.hash = hash - this.size = size - this.from = from - this.to = to - } -} - -/** Class representing a mojang library. */ -class Library extends Asset { - - /** - * Converts the process.platform OS names to match mojang's OS names. - */ - static mojangFriendlyOS(){ - const opSys = process.platform - if (opSys === 'darwin') { - return 'osx' - } else if (opSys === 'win32'){ - return 'windows' - } else if (opSys === 'linux'){ - return 'linux' - } else { - return 'unknown_os' - } - } - - /** - * Checks whether or not a library is valid for download on a particular OS, following - * the rule format specified in the mojang version data index. If the allow property has - * an OS specified, then the library can ONLY be downloaded on that OS. If the disallow - * property has instead specified an OS, the library can be downloaded on any OS EXCLUDING - * the one specified. - * - * If the rules are undefined, the natives property will be checked for a matching entry - * for the current OS. - * - * @param {Array.} rules The Library's download rules. - * @param {Object} natives The Library's natives object. - * @returns {boolean} True if the Library follows the specified rules, otherwise false. - */ - static validateRules(rules, natives){ - if(rules == null) { - if(natives == null) { - return true - } else { - return natives[Library.mojangFriendlyOS()] != null - } - } - - for(let rule of rules){ - const action = rule.action - const osProp = rule.os - if(action != null && osProp != null){ - const osName = osProp.name - const osMoj = Library.mojangFriendlyOS() - if(action === 'allow'){ - return osName === osMoj - } else if(action === 'disallow'){ - return osName !== osMoj - } - } - } - return true - } -} - -class DistroModule extends Asset { - - /** - * Create a DistroModule. This is for processing, - * not equivalent to the module objects in the - * distro index. - * - * @param {any} id The id of the asset. - * @param {string} hash The hash value of the asset. - * @param {number} size The size in bytes of the asset. - * @param {string} from The url where the asset can be found. - * @param {string} to The absolute local file path of the asset. - * @param {string} type The the module type. - */ - constructor(id, hash, size, from, to, type){ - super(id, hash, size, from, to) - this.type = type - } - -} - -/** - * Class representing a download tracker. This is used to store meta data - * about a download queue, including the queue itself. - */ -class DLTracker { - - /** - * Create a DLTracker - * - * @param {Array.} dlqueue An array containing assets queued for download. - * @param {number} dlsize The combined size of each asset in the download queue array. - * @param {function(Asset)} callback Optional callback which is called when an asset finishes downloading. - */ - constructor(dlqueue, dlsize, callback = null){ - this.dlqueue = dlqueue - this.dlsize = dlsize - this.callback = callback - } - -} - -class Util { - - /** - * Returns true if the actual version is greater than - * or equal to the desired version. - * - * @param {string} desired The desired version. - * @param {string} actual The actual version. - */ - static mcVersionAtLeast(desired, actual){ - const des = desired.split('.') - const act = actual.split('.') - - for(let i=0; i= parseInt(des[i]))){ - return false - } - } - return true - } - - static isForgeGradle3(mcVersion, forgeVersion) { - - if(Util.mcVersionAtLeast('1.13', mcVersion)) { - return true - } - - try { - - const forgeVer = forgeVersion.split('-')[1] - - const maxFG2 = [14, 23, 5, 2847] - const verSplit = forgeVer.split('.').map(v => Number(v)) - - for(let i=0; i maxFG2[i]) { - return true - } else if(verSplit[i] < maxFG2[i]) { - return false - } - } - - return false - - } catch(err) { - throw new Error('Forge version is complex (changed).. launcher requires a patch.') - } - } - - static isAutoconnectBroken(forgeVersion) { - - const minWorking = [31, 2, 15] - const verSplit = forgeVersion.split('.').map(v => Number(v)) - - if(verSplit[0] === 31) { - for(let i=0; i minWorking[i]) { - return false - } else if(verSplit[i] < minWorking[i]) { - return true - } - } - } - - return false - } - -} - - -class JavaGuard extends EventEmitter { - - constructor(mcVersion){ - super() - this.mcVersion = mcVersion - this.logger = LoggerUtil.getLogger('JavaGuard') - } - - /** - * @typedef OpenJDKData - * @property {string} uri The base uri of the JRE. - * @property {number} size The size of the download. - * @property {string} name The name of the artifact. - */ - - /** - * Fetch the last open JDK binary. - * - * HOTFIX: Uses Corretto 8 for macOS. - * See: https://github.com/dscalzi/HeliosLauncher/issues/70 - * See: https://github.com/AdoptOpenJDK/openjdk-support/issues/101 - * - * @param {string} major The major version of Java to fetch. - * - * @returns {Promise.} Promise which resolved to an object containing the JRE download data. - */ - static _latestOpenJDK(major = '8'){ - - if(process.platform === 'darwin') { - return this._latestCorretto(major) - } else { - return this._latestAdoptium(major) - } - } - - static _latestAdoptium(major) { - - const majorNum = Number(major) - const sanitizedOS = process.platform === 'win32' ? 'windows' : (process.platform === 'darwin' ? 'mac' : process.platform) - const url = `https://api.adoptium.net/v3/assets/latest/${major}/hotspot?vendor=eclipse` - - return new Promise((resolve, reject) => { - request({url, json: true}, (err, resp, body) => { - if(!err && body.length > 0){ - - const targetBinary = body.find(entry => { - return entry.version.major === majorNum - && entry.binary.os === sanitizedOS - && entry.binary.image_type === 'jdk' - && entry.binary.architecture === 'x64' - }) - - if(targetBinary != null) { - resolve({ - uri: targetBinary.binary.package.link, - size: targetBinary.binary.package.size, - name: targetBinary.binary.package.name - }) - } else { - resolve(null) - } - } else { - resolve(null) - } - }) - }) - } - - static _latestCorretto(major) { - - let sanitizedOS, ext - - switch(process.platform) { - case 'win32': - sanitizedOS = 'windows' - ext = 'zip' - break - case 'darwin': - sanitizedOS = 'macos' - ext = 'tar.gz' - break - case 'linux': - sanitizedOS = 'linux' - ext = 'tar.gz' - break - default: - sanitizedOS = process.platform - ext = 'tar.gz' - break - } - - const arch = isARM64 ? 'aarch64' : 'x64' - const url = `https://corretto.aws/downloads/latest/amazon-corretto-${major}-${arch}-${sanitizedOS}-jdk.${ext}` - - return new Promise((resolve, reject) => { - request.head({url, json: true}, (err, resp) => { - if(!err && resp.statusCode === 200){ - resolve({ - uri: url, - size: parseInt(resp.headers['content-length']), - name: url.substr(url.lastIndexOf('/')+1) - }) - } else { - resolve(null) - } - }) - }) - - } - - /** - * Returns the path of the OS-specific executable for the given Java - * installation. Supported OS's are win32, darwin, linux. - * - * @param {string} rootDir The root directory of the Java installation. - * @returns {string} The path to the Java executable. - */ - static javaExecFromRoot(rootDir){ - if(process.platform === 'win32'){ - return path.join(rootDir, 'bin', 'javaw.exe') - } else if(process.platform === 'darwin'){ - return path.join(rootDir, 'Contents', 'Home', 'bin', 'java') - } else if(process.platform === 'linux'){ - return path.join(rootDir, 'bin', 'java') - } - return rootDir - } - - /** - * Check to see if the given path points to a Java executable. - * - * @param {string} pth The path to check against. - * @returns {boolean} True if the path points to a Java executable, otherwise false. - */ - static isJavaExecPath(pth){ - if(pth == null) { - return false - } - if(process.platform === 'win32'){ - return pth.endsWith(path.join('bin', 'javaw.exe')) - } else if(process.platform === 'darwin'){ - return pth.endsWith(path.join('bin', 'java')) - } else if(process.platform === 'linux'){ - return pth.endsWith(path.join('bin', 'java')) - } - return false - } - - /** - * Load Mojang's launcher.json file. - * - * @returns {Promise.} Promise which resolves to Mojang's launcher.json object. - */ - static loadMojangLauncherData(){ - return new Promise((resolve, reject) => { - request.get('https://launchermeta.mojang.com/mc/launcher.json', (err, resp, body) => { - if(err){ - resolve(null) - } else { - resolve(JSON.parse(body)) - } - }) - }) - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Dynamically detects the formatting - * to use. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - static parseJavaRuntimeVersion(verString){ - const major = verString.split('.')[0] - if(major == 1){ - return JavaGuard._parseJavaRuntimeVersion_8(verString) - } else { - return JavaGuard._parseJavaRuntimeVersion_9(verString) - } - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Uses Java 8 formatting. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - static _parseJavaRuntimeVersion_8(verString){ - // 1.{major}.0_{update}-b{build} - // ex. 1.8.0_152-b16 - const ret = {} - let pts = verString.split('-') - ret.build = parseInt(pts[1].substring(1)) - pts = pts[0].split('_') - ret.update = parseInt(pts[1]) - ret.major = parseInt(pts[0].split('.')[1]) - return ret - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Uses Java 9+ formatting. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - static _parseJavaRuntimeVersion_9(verString){ - // {major}.{minor}.{revision}+{build} - // ex. 10.0.2+13 - const ret = {} - let pts = verString.split('+') - ret.build = parseInt(pts[1]) - pts = pts[0].split('.') - ret.major = parseInt(pts[0]) - ret.minor = parseInt(pts[1]) - ret.revision = parseInt(pts[2]) - return ret - } - - /** - * Validates the output of a JVM's properties. Currently validates that a JRE is x64 - * and that the major = 8, update > 52. - * - * @param {string} stderr The output to validate. - * - * @returns {Promise.} A promise which resolves to a meta object about the JVM. - * The validity is stored inside the `valid` property. - */ - _validateJVMProperties(stderr){ - const res = stderr - const props = res.split('\n') - - const goal = 2 - let checksum = 0 - - const meta = {} - - for(let i=0; i -1){ - let arch = props[i].split('=')[1].trim() - arch = parseInt(arch) - this.logger.debug(props[i].trim()) - if(arch === 64){ - meta.arch = arch - ++checksum - if(checksum === goal){ - break - } - } - } else if(props[i].indexOf('java.runtime.version') > -1){ - let verString = props[i].split('=')[1].trim() - this.logger.debug(props[i].trim()) - const verOb = JavaGuard.parseJavaRuntimeVersion(verString) - // TODO implement a support matrix eventually. Right now this is good enough - // 1.7-1.16 = Java 8 - // 1.17+ = Java 17 - // Actual support may vary, but we're going with this rule for simplicity. - if(verOb.major < 9){ - // Java 8 - if(!Util.mcVersionAtLeast('1.17', this.mcVersion)){ - if(verOb.major === 8 && verOb.update > 52){ - meta.version = verOb - ++checksum - if(checksum === goal){ - break - } - } - } - } else if(verOb.major >= 17) { - // Java 9+ - if(Util.mcVersionAtLeast('1.17', this.mcVersion)){ - meta.version = verOb - ++checksum - if(checksum === goal){ - break - } - } - } - // Space included so we get only the vendor. - } else if(props[i].lastIndexOf('java.vendor ') > -1) { - let vendorName = props[i].split('=')[1].trim() - this.logger.debug(props[i].trim()) - meta.vendor = vendorName - } else if (props[i].indexOf('os.arch') > -1) { - meta.isARM = props[i].split('=')[1].trim() === 'aarch64' - } - } - - meta.valid = checksum === goal - - return meta - } - - /** - * Validates that a Java binary is at least 64 bit. This makes use of the non-standard - * command line option -XshowSettings:properties. The output of this contains a property, - * sun.arch.data.model = ARCH, in which ARCH is either 32 or 64. This option is supported - * in Java 8 and 9. Since this is a non-standard option. This will resolve to true if - * the function's code throws errors. That would indicate that the option is changed or - * removed. - * - * @param {string} binaryExecPath Path to the java executable we wish to validate. - * - * @returns {Promise.} A promise which resolves to a meta object about the JVM. - * The validity is stored inside the `valid` property. - */ - _validateJavaBinary(binaryExecPath){ - - return new Promise((resolve, reject) => { - if(!JavaGuard.isJavaExecPath(binaryExecPath)){ - resolve({valid: false}) - } else if(fs.existsSync(binaryExecPath)){ - // Workaround (javaw.exe no longer outputs this information.) - this.logger.debug(typeof binaryExecPath) - if(binaryExecPath.indexOf('javaw.exe') > -1) { - binaryExecPath.replace('javaw.exe', 'java.exe') - } - child_process.exec('"' + binaryExecPath + '" -XshowSettings:properties', (err, stdout, stderr) => { - try { - // Output is stored in stderr? - resolve(this._validateJVMProperties(stderr)) - } catch (err){ - // Output format might have changed, validation cannot be completed. - resolve({valid: false}) - } - }) - } else { - resolve({valid: false}) - } - }) - - } - - /** - * Checks for the presence of the environment variable JAVA_HOME. If it exits, we will check - * to see if the value points to a path which exists. If the path exits, the path is returned. - * - * @returns {string} The path defined by JAVA_HOME, if it exists. Otherwise null. - */ - static _scanJavaHome(){ - const jHome = process.env.JAVA_HOME - try { - let res = fs.existsSync(jHome) - return res ? jHome : null - } catch (err) { - // Malformed JAVA_HOME property. - return null - } - } - - /** - * Scans the registry for 64-bit Java entries. The paths of each entry are added to - * a set and returned. Currently, only Java 8 (1.8) is supported. - * - * @returns {Promise.>} A promise which resolves to a set of 64-bit Java root - * paths found in the registry. - */ - static _scanRegistry(){ - - return new Promise((resolve, reject) => { - // Keys for Java v9.0.0 and later: - // 'SOFTWARE\\JavaSoft\\JRE' - // 'SOFTWARE\\JavaSoft\\JDK' - // Forge does not yet support Java 9, therefore we do not. - - // Keys for Java 1.8 and prior: - const regKeys = [ - '\\SOFTWARE\\JavaSoft\\Java Runtime Environment', - '\\SOFTWARE\\JavaSoft\\Java Development Kit' - ] - - let keysDone = 0 - - const candidates = new Set() - - for(let i=0; i { - if(exists) { - key.keys((err, javaVers) => { - if(err){ - keysDone++ - console.error(err) - - // REG KEY DONE - // DUE TO ERROR - if(keysDone === regKeys.length){ - resolve(candidates) - } - } else { - if(javaVers.length === 0){ - // REG KEY DONE - // NO SUBKEYS - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } else { - - let numDone = 0 - - for(let j=0; j { - const jHome = res.value - if(jHome.indexOf('(x86)') === -1){ - candidates.add(jHome) - } - - // SUBKEY DONE - - numDone++ - if(numDone === javaVers.length){ - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } - }) - } else { - - // SUBKEY DONE - // NOT JAVA 8 - - numDone++ - if(numDone === javaVers.length){ - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } - } - } - } - } - }) - } else { - - // REG KEY DONE - // DUE TO NON-EXISTANCE - - keysDone++ - if(keysDone === regKeys.length){ - resolve(candidates) - } - } - }) - } - - }) - - } - - /** - * See if JRE exists in the Internet Plug-Ins folder. - * - * @returns {string} The path of the JRE if found, otherwise null. - */ - static _scanInternetPlugins(){ - // /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java - const pth = '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin' - const res = fs.existsSync(JavaGuard.javaExecFromRoot(pth)) - return res ? pth : null - } - - /** - * Scan a directory for root JVM folders. - * - * @param {string} scanDir The directory to scan. - * @returns {Promise.>} A promise which resolves to a set of the discovered - * root JVM folders. - */ - static async _scanFileSystem(scanDir){ - - let res = new Set() - - if(await fs.pathExists(scanDir)) { - - const files = await fs.readdir(scanDir) - for(let i=0; i} rootSet A set of JVM root strings to validate. - * @returns {Promise.} A promise which resolves to an array of meta objects - * for each valid JVM root directory. - */ - async _validateJavaRootSet(rootSet){ - - const rootArr = Array.from(rootSet) - const validArr = [] - - for(let i=0; i { - - if(a.version.major === b.version.major){ - - if(a.version.major < 9){ - // Java 8 - if(a.version.update === b.version.update){ - if(a.version.build === b.version.build){ - - // Same version, give priority to JRE. - if(a.execPath.toLowerCase().indexOf('jdk') > -1){ - return b.execPath.toLowerCase().indexOf('jdk') > -1 ? 0 : 1 - } else { - return -1 - } - - } else { - return a.version.build > b.version.build ? -1 : 1 - } - } else { - return a.version.update > b.version.update ? -1 : 1 - } - } else { - // Java 9+ - if(a.version.minor === b.version.minor){ - if(a.version.revision === b.version.revision){ - - // Same version, give priority to JRE. - if(a.execPath.toLowerCase().indexOf('jdk') > -1){ - return b.execPath.toLowerCase().indexOf('jdk') > -1 ? 0 : 1 - } else { - return -1 - } - - } else { - return a.version.revision > b.version.revision ? -1 : 1 - } - } else { - return a.version.minor > b.version.minor ? -1 : 1 - } - } - - } else { - return a.version.major > b.version.major ? -1 : 1 - } - }) - - return retArr - } - - /** - * Attempts to find a valid x64 installation of Java on Windows machines. - * Possible paths will be pulled from the registry and the JAVA_HOME environment - * variable. The paths will be sorted with higher versions preceeding lower, and - * JREs preceeding JDKs. The binaries at the sorted paths will then be validated. - * The first validated is returned. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - async _win32JavaValidate(dataDir){ - - // Get possible paths from the registry. - let pathSet1 = await JavaGuard._scanRegistry() - if(pathSet1.size === 0){ - - // Do a manual file system scan of program files. - // Check all drives - const driveMounts = nodeDiskInfo.getDiskInfoSync().map(({ mounted }) => mounted) - for(const mount of driveMounts) { - pathSet1 = new Set([ - ...pathSet1, - ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\Java`)), - ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\Eclipse Adoptium`)), - ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\Eclipse Foundation`)), - ...(await JavaGuard._scanFileSystem(`${mount}\\Program Files\\AdoptOpenJDK`)) - ]) - } - - } - - // Get possible paths from the data directory. - const pathSet2 = await JavaGuard._scanFileSystem(path.join(dataDir, 'runtime', 'x64')) - - // Merge the results. - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Validate JAVA_HOME. - const jHome = JavaGuard._scanJavaHome() - if(jHome != null && jHome.indexOf('(x86)') === -1){ - uberSet.add(jHome) - } - - let pathArr = await this._validateJavaRootSet(uberSet) - pathArr = JavaGuard._sortValidJavaArray(pathArr) - - if(pathArr.length > 0){ - return pathArr[0].execPath - } else { - return null - } - - } - - /** - * Attempts to find a valid x64 installation of Java on MacOS. - * The system JVM directory is scanned for possible installations. - * The JAVA_HOME enviroment variable and internet plugins directory - * are also scanned and validated. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - * - * Added: On the system with ARM architecture attempts to find aarch64 Java. - * - */ - async _darwinJavaValidate(dataDir){ - - const pathSet1 = await JavaGuard._scanFileSystem('/Library/Java/JavaVirtualMachines') - const pathSet2 = await JavaGuard._scanFileSystem(path.join(dataDir, 'runtime', 'x64')) - - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Check Internet Plugins folder. - const iPPath = JavaGuard._scanInternetPlugins() - if(iPPath != null){ - uberSet.add(iPPath) - } - - // Check the JAVA_HOME environment variable. - let jHome = JavaGuard._scanJavaHome() - if(jHome != null){ - // Ensure we are at the absolute root. - if(jHome.contains('/Contents/Home')){ - jHome = jHome.substring(0, jHome.indexOf('/Contents/Home')) - } - uberSet.add(jHome) - } - - let pathArr = await this._validateJavaRootSet(uberSet) - pathArr = JavaGuard._sortValidJavaArray(pathArr) - - if(pathArr.length > 0){ - - // TODO Revise this a bit, seems to work for now. Discovery logic should - // probably just filter out the invalid architectures before it even - // gets to this point. - if (isARM64) { - return pathArr.find(({ isARM }) => isARM)?.execPath ?? null - } else { - return pathArr.find(({ isARM }) => !isARM)?.execPath ?? null - } - - } else { - return null - } - } - - /** - * Attempts to find a valid x64 installation of Java on Linux. - * The system JVM directory is scanned for possible installations. - * The JAVA_HOME enviroment variable is also scanned and validated. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - async _linuxJavaValidate(dataDir){ - - const pathSet1 = await JavaGuard._scanFileSystem('/usr/lib/jvm') - const pathSet2 = await JavaGuard._scanFileSystem(path.join(dataDir, 'runtime', 'x64')) - - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Validate JAVA_HOME - const jHome = JavaGuard._scanJavaHome() - if(jHome != null){ - uberSet.add(jHome) - } - - let pathArr = await this._validateJavaRootSet(uberSet) - pathArr = JavaGuard._sortValidJavaArray(pathArr) - - if(pathArr.length > 0){ - return pathArr[0].execPath - } else { - return null - } - } - - /** - * Retrieve the path of a valid x64 Java installation. - * - * @param {string} dataDir The base launcher directory. - * @returns {string} A path to a valid x64 Java installation, null if none found. - */ - async validateJava(dataDir){ - return await this['_' + process.platform + 'JavaValidate'](dataDir) - } - -} - - - - -/** - * Central object class used for control flow. This object stores data about - * categories of downloads. Each category is assigned an identifier with a - * DLTracker object as its value. Combined information is also stored, such as - * the total size of all the queued files in each category. This event is used - * to emit events so that external modules can listen into processing done in - * this module. - */ -class AssetGuard extends EventEmitter { - - static logger = LoggerUtil.getLogger('AssetGuard') - - /** - * Create an instance of AssetGuard. - * On creation the object's properties are never-null default - * values. Each identifier is resolved to an empty DLTracker. - * - * @param {string} commonPath The common path for shared game files. - * @param {string} javaexec The path to a java executable which will be used - * to finalize installation. - */ - constructor(commonPath, javaexec){ - super() - this.totaldlsize = 0 - this.progress = 0 - this.assets = new DLTracker([], 0) - this.libraries = new DLTracker([], 0) - this.files = new DLTracker([], 0) - this.forge = new DLTracker([], 0) - this.java = new DLTracker([], 0) - this.extractQueue = [] - this.commonPath = commonPath - this.javaexec = javaexec - } - - // Static Utility Functions - // #region - - // Static Hash Validation Functions - // #region - - /** - * Calculates the hash for a file using the specified algorithm. - * - * @param {Buffer} buf The buffer containing file data. - * @param {string} algo The hash algorithm. - * @returns {string} The calculated hash in hex. - */ - static _calculateHash(buf, algo){ - return crypto.createHash(algo).update(buf).digest('hex') - } - - /** - * Used to parse a checksums file. This is specifically designed for - * the checksums.sha1 files found inside the forge scala dependencies. - * - * @param {string} content The string content of the checksums file. - * @returns {Object} An object with keys being the file names, and values being the hashes. - */ - static _parseChecksumsFile(content){ - let finalContent = {} - let lines = content.split('\n') - for(let i=0; i} checksums The checksums listed in the forge version index. - * @returns {boolean} True if the file exists and the hashes match, otherwise false. - */ - static _validateForgeChecksum(filePath, checksums){ - if(fs.existsSync(filePath)){ - if(checksums == null || checksums.length === 0){ - return true - } - let buf = fs.readFileSync(filePath) - let calcdhash = AssetGuard._calculateHash(buf, 'sha1') - let valid = checksums.includes(calcdhash) - if(!valid && filePath.endsWith('.jar')){ - valid = AssetGuard._validateForgeJar(filePath, checksums) - } - return valid - } - return false - } - - /** - * Validates a forge jar file dependency who declares a checksums.sha1 file. - * This can be an expensive task as it usually requires that we calculate thousands - * of hashes. - * - * @param {Buffer} buf The buffer of the jar file. - * @param {Array.} checksums The checksums listed in the forge version index. - * @returns {boolean} True if all hashes declared in the checksums.sha1 file match the actual hashes. - */ - static _validateForgeJar(buf, checksums){ - // Double pass method was the quickest I found. I tried a version where we store data - // to only require a single pass, plus some quick cleanup but that seemed to take slightly more time. - - const hashes = {} - let expected = {} - - const zip = new AdmZip(buf) - const zipEntries = zip.getEntries() - - //First pass - for(let i=0; i} filePaths The paths of the files to be extracted and unpacked. - * @returns {Promise.} An empty promise to indicate the extraction has completed. - */ - static _extractPackXZ(filePaths, javaExecutable){ - const extractLogger = LoggerUtil.getLogger('PackXZExtract') - extractLogger.info('Starting') - return new Promise((resolve, reject) => { - - let libPath - if(isDev){ - libPath = path.join(process.cwd(), 'libraries', 'java', 'PackXZExtract.jar') - } else { - if(process.platform === 'darwin'){ - libPath = path.join(process.cwd(),'Contents', 'Resources', 'libraries', 'java', 'PackXZExtract.jar') - } else { - libPath = path.join(process.cwd(), 'resources', 'libraries', 'java', 'PackXZExtract.jar') - } - } - - const filePath = filePaths.join(',') - const child = child_process.spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath]) - child.stdout.on('data', (data) => { - extractLogger.info(data.toString('utf8')) - }) - child.stderr.on('data', (data) => { - extractLogger.info(data.toString('utf8')) - }) - child.on('close', (code, signal) => { - extractLogger.info('Exited with code', code) - resolve() - }) - }) - } - - /** - * Function which finalizes the forge installation process. This creates a 'version' - * instance for forge and saves its version.json file into that instance. If that - * instance already exists, the contents of the version.json file are read and returned - * in a promise. - * - * @param {Asset} asset The Asset object representing Forge. - * @param {string} commonPath The common path for shared game files. - * @returns {Promise.} A promise which resolves to the contents of forge's version.json. - */ - static _finalizeForgeAsset(asset, commonPath){ - return new Promise((resolve, reject) => { - fs.readFile(asset.to, (err, data) => { - const zip = new AdmZip(data) - const zipEntries = zip.getEntries() - - for(let i=0; i} Promise which resolves to the version data object. - */ - loadVersionData(version, force = false){ - const self = this - return new Promise(async (resolve, reject) => { - const versionPath = path.join(self.commonPath, 'versions', version) - const versionFile = path.join(versionPath, version + '.json') - if(!fs.existsSync(versionFile) || force){ - const url = await self._getVersionDataUrl(version) - //This download will never be tracked as it's essential and trivial. - AssetGuard.logger.info('Preparing download of ' + version + ' assets.') - fs.ensureDirSync(versionPath) - const stream = request(url).pipe(fs.createWriteStream(versionFile)) - stream.on('finish', () => { - resolve(JSON.parse(fs.readFileSync(versionFile))) - }) - } else { - resolve(JSON.parse(fs.readFileSync(versionFile))) - } - }) - } - - /** - * Parses Mojang's version manifest and retrieves the url of the version - * data index. - * - * @param {string} version The version to lookup. - * @returns {Promise.} Promise which resolves to the url of the version data index. - * If the version could not be found, resolves to null. - */ - _getVersionDataUrl(version){ - return new Promise((resolve, reject) => { - request('https://launchermeta.mojang.com/mc/game/version_manifest.json', (error, resp, body) => { - if(error){ - reject(error) - } else { - const manifest = JSON.parse(body) - - for(let v of manifest.versions){ - if(v.id === version){ - resolve(v.url) - } - } - - resolve(null) - } - }) - }) - } - - - // Asset (Category=''') Validation Functions - // #region - - /** - * Public asset validation function. This function will handle the validation of assets. - * It will parse the asset index specified in the version data, analyzing each - * asset entry. In this analysis it will check to see if the local file exists and is valid. - * If not, it will be added to the download queue for the 'assets' identifier. - * - * @param {Object} versionData The version data for the assets. - * @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - validateAssets(versionData, force = false){ - const self = this - return new Promise((resolve, reject) => { - self._assetChainIndexData(versionData, force).then(() => { - resolve() - }) - }) - } - - //Chain the asset tasks to provide full async. The below functions are private. - /** - * Private function used to chain the asset validation process. This function retrieves - * the index data. - * @param {Object} versionData - * @param {boolean} force - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - _assetChainIndexData(versionData, force = false){ - const self = this - return new Promise((resolve, reject) => { - //Asset index constants. - const assetIndex = versionData.assetIndex - const name = assetIndex.id + '.json' - const indexPath = path.join(self.commonPath, 'assets', 'indexes') - const assetIndexLoc = path.join(indexPath, name) - - let data = null - if(!fs.existsSync(assetIndexLoc) || force){ - AssetGuard.logger.info('Downloading ' + versionData.id + ' asset index.') - fs.ensureDirSync(indexPath) - const stream = request(assetIndex.url).pipe(fs.createWriteStream(assetIndexLoc)) - stream.on('finish', () => { - data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8')) - self._assetChainValidateAssets(versionData, data).then(() => { - resolve() - }) - }) - } else { - data = JSON.parse(fs.readFileSync(assetIndexLoc, 'utf-8')) - self._assetChainValidateAssets(versionData, data).then(() => { - resolve() - }) - } - }) - } - - /** - * Private function used to chain the asset validation process. This function processes - * the assets and enqueues missing or invalid files. - * @param {Object} versionData - * @param {boolean} force - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - _assetChainValidateAssets(versionData, indexData){ - const self = this - return new Promise((resolve, reject) => { - - //Asset constants - const resourceURL = 'https://resources.download.minecraft.net/' - const localPath = path.join(self.commonPath, 'assets') - const objectPath = path.join(localPath, 'objects') - - const assetDlQueue = [] - let dlSize = 0 - let acc = 0 - const total = Object.keys(indexData.objects).length - //const objKeys = Object.keys(data.objects) - async.forEachOfLimit(indexData.objects, 10, (value, key, cb) => { - acc++ - self.emit('progress', 'assets', acc, total) - const hash = value.hash - const assetName = path.join(hash.substring(0, 2), hash) - const urlName = hash.substring(0, 2) + '/' + hash - const ast = new Asset(key, hash, value.size, resourceURL + urlName, path.join(objectPath, assetName)) - if(!AssetGuard._validateLocal(ast.to, 'sha1', ast.hash)){ - dlSize += (ast.size*1) - assetDlQueue.push(ast) - } - cb() - }, (err) => { - self.assets = new DLTracker(assetDlQueue, dlSize) - resolve() - }) - }) - } - - // #endregion - - // Library (Category=''') Validation Functions - // #region - - /** - * Public library validation function. This function will handle the validation of libraries. - * It will parse the version data, analyzing each library entry. In this analysis, it will - * check to see if the local file exists and is valid. If not, it will be added to the download - * queue for the 'libraries' identifier. - * - * @param {Object} versionData The version data for the assets. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - validateLibraries(versionData){ - const self = this - return new Promise((resolve, reject) => { - - const libArr = versionData.libraries - const libPath = path.join(self.commonPath, 'libraries') - - const libDlQueue = [] - let dlSize = 0 - - //Check validity of each library. If the hashs don't match, download the library. - async.eachLimit(libArr, 5, (lib, cb) => { - if(Library.validateRules(lib.rules, lib.natives)){ - let artifact = (lib.natives == null) ? lib.downloads.artifact : lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))] - const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, path.join(libPath, artifact.path)) - if(!AssetGuard._validateLocal(libItm.to, 'sha1', libItm.hash)){ - dlSize += (libItm.size*1) - libDlQueue.push(libItm) - } - } - cb() - }, (err) => { - self.libraries = new DLTracker(libDlQueue, dlSize) - resolve() - }) - }) - } - - // #endregion - - // Miscellaneous (Category=files) Validation Functions - // #region - - /** - * Public miscellaneous mojang file validation function. These files will be enqueued under - * the 'files' identifier. - * - * @param {Object} versionData The version data for the assets. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - validateMiscellaneous(versionData){ - const self = this - return new Promise(async (resolve, reject) => { - await self.validateClient(versionData) - await self.validateLogConfig(versionData) - resolve() - }) - } - - /** - * Validate client file - artifact renamed from client.jar to '{version}'.jar. - * - * @param {Object} versionData The version data for the assets. - * @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - validateClient(versionData, force = false){ - const self = this - return new Promise((resolve, reject) => { - const clientData = versionData.downloads.client - const version = versionData.id - const targetPath = path.join(self.commonPath, 'versions', version) - const targetFile = version + '.jar' - - let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, path.join(targetPath, targetFile)) - - if(!AssetGuard._validateLocal(client.to, 'sha1', client.hash) || force){ - self.files.dlqueue.push(client) - self.files.dlsize += client.size*1 - resolve() - } else { - resolve() - } - }) - } - - /** - * Validate log config. - * - * @param {Object} versionData The version data for the assets. - * @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - validateLogConfig(versionData){ - const self = this - return new Promise((resolve, reject) => { - const client = versionData.logging.client - const file = client.file - const targetPath = path.join(self.commonPath, 'assets', 'log_configs') - - let logConfig = new Asset(file.id, file.sha1, file.size, file.url, path.join(targetPath, file.id)) - - if(!AssetGuard._validateLocal(logConfig.to, 'sha1', logConfig.hash)){ - self.files.dlqueue.push(logConfig) - self.files.dlsize += logConfig.size*1 - resolve() - } else { - resolve() - } - }) - } - - // #endregion - - // Distribution (Category=forge) Validation Functions - // #region - - /** - * Validate the distribution. - * - * @param {Server} server The Server to validate. - * @returns {Promise.} A promise which resolves to the server distribution object. - */ - validateDistribution(server){ - const self = this - return new Promise((resolve, reject) => { - self.forge = self._parseDistroModules(server.getModules(), server.getMinecraftVersion(), server.getID()) - resolve(server) - }) - } - - _parseDistroModules(modules, version, servid){ - let alist = [] - let asize = 0 - for(let ob of modules){ - let obArtifact = ob.getArtifact() - let obPath = obArtifact.getPath() - let artifact = new DistroModule(ob.getIdentifier(), obArtifact.getHash(), obArtifact.getSize(), obArtifact.getURL(), obPath, ob.getType()) - const validationPath = obPath.toLowerCase().endsWith('.pack.xz') ? obPath.substring(0, obPath.toLowerCase().lastIndexOf('.pack.xz')) : obPath - if(!AssetGuard._validateLocal(validationPath, 'MD5', artifact.hash)){ - asize += artifact.size*1 - alist.push(artifact) - if(validationPath !== obPath) this.extractQueue.push(obPath) - } - //Recursively process the submodules then combine the results. - if(ob.getSubModules() != null){ - let dltrack = this._parseDistroModules(ob.getSubModules(), version, servid) - asize += dltrack.dlsize*1 - alist = alist.concat(dltrack.dlqueue) - } - } - - return new DLTracker(alist, asize) - } - - /** - * Loads Forge's version.json data into memory for the specified server id. - * - * @param {string} server The Server to load Forge data for. - * @returns {Promise.} A promise which resolves to Forge's version.json data. - */ - loadForgeData(server){ - const self = this - return new Promise(async (resolve, reject) => { - const modules = server.getModules() - for(let ob of modules){ - const type = ob.getType() - if(type === DistroManager.Types.ForgeHosted || type === DistroManager.Types.Forge){ - if(Util.isForgeGradle3(server.getMinecraftVersion(), ob.getVersion())){ - // Read Manifest - for(let sub of ob.getSubModules()){ - if(sub.getType() === DistroManager.Types.VersionManifest){ - resolve(JSON.parse(fs.readFileSync(sub.getArtifact().getPath(), 'utf-8'))) - return - } - } - reject('No forge version manifest found!') - return - } else { - let obArtifact = ob.getArtifact() - let obPath = obArtifact.getPath() - let asset = new DistroModule(ob.getIdentifier(), obArtifact.getHash(), obArtifact.getSize(), obArtifact.getURL(), obPath, type) - try { - let forgeData = await AssetGuard._finalizeForgeAsset(asset, self.commonPath) - resolve(forgeData) - } catch (err){ - reject(err) - } - return - } - } - } - reject('No forge module found!') - }) - } - - _parseForgeLibraries(){ - /* TODO - * Forge asset validations are already implemented. When there's nothing much - * to work on, implement forge downloads using forge's version.json. This is to - * have the code on standby if we ever need it (since it's half implemented already). - */ - } - - // #endregion - - // Java (Category=''') Validation (download) Functions - // #region - - _enqueueOpenJDK(dataDir, mcVersion){ - return new Promise((resolve, reject) => { - const major = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8' - JavaGuard._latestOpenJDK(major).then(verData => { - if(verData != null){ - - dataDir = path.join(dataDir, 'runtime', 'x64') - const fDir = path.join(dataDir, verData.name) - const jre = new Asset(verData.name, null, verData.size, verData.uri, fDir) - this.java = new DLTracker([jre], jre.size, (a, self) => { - if(verData.name.endsWith('zip')){ - - this._extractJdkZip(a.to, dataDir, self) - - } else { - // Tar.gz - let h = null - fs.createReadStream(a.to) - .on('error', err => AssetGuard.logger.error(err)) - .pipe(zlib.createGunzip()) - .on('error', err => AssetGuard.logger.error(err)) - .pipe(tar.extract(dataDir, { - map: (header) => { - if(h == null){ - h = header.name - } - } - })) - .on('error', err => AssetGuard.logger.error(err)) - .on('finish', () => { - fs.unlink(a.to, err => { - if(err){ - AssetGuard.logger.error(err) - } - if(h.indexOf('/') > -1){ - h = h.substring(0, h.indexOf('/')) - } - const pos = path.join(dataDir, h) - self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - }) - }) - } - }) - resolve(true) - - } else { - resolve(false) - } - }) - }) - - } - - async _extractJdkZip(zipPath, runtimeDir, self) { - - const zip = new StreamZip.async({ - file: zipPath, - storeEntries: true - }) - - let pos = '' - try { - const entries = await zip.entries() - pos = path.join(runtimeDir, Object.keys(entries)[0]) - - AssetGuard.logger.info('Extracting jdk..') - await zip.extract(null, runtimeDir) - AssetGuard.logger.info('Cleaning up..') - await fs.remove(zipPath) - AssetGuard.logger.info('Jdk extraction complete.') - - } catch(err) { - AssetGuard.logger.error(err) - } finally { - zip.close() - self.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - } - } - - // _enqueueMojangJRE(dir){ - // return new Promise((resolve, reject) => { - // // Mojang does not host the JRE for linux. - // if(process.platform === 'linux'){ - // resolve(false) - // } - // AssetGuard.loadMojangLauncherData().then(data => { - // if(data != null) { - - // try { - // const mJRE = data[Library.mojangFriendlyOS()]['64'].jre - // const url = mJRE.url - - // request.head(url, (err, resp, body) => { - // if(err){ - // resolve(false) - // } else { - // const name = url.substring(url.lastIndexOf('/')+1) - // const fDir = path.join(dir, name) - // const jre = new Asset('jre' + mJRE.version, mJRE.sha1, resp.headers['content-length'], url, fDir) - // this.java = new DLTracker([jre], jre.size, a => { - // fs.readFile(a.to, (err, data) => { - // // Data buffer needs to be decompressed from lzma, - // // not really possible using node.js - // }) - // }) - // } - // }) - // } catch (err){ - // resolve(false) - // } - - // } - // }) - // }) - // } - - - // #endregion - - // #endregion - - // Control Flow Functions - // #region - - /** - * Initiate an async download process for an AssetGuard DLTracker. - * - * @param {string} identifier The identifier of the AssetGuard DLTracker. - * @param {number} limit Optional. The number of async processes to run in parallel. - * @returns {boolean} True if the process began, otherwise false. - */ - startAsyncProcess(identifier, limit = 5){ - - const self = this - const dlTracker = this[identifier] - const dlQueue = dlTracker.dlqueue - - if(dlQueue.length > 0){ - AssetGuard.logger.info('DLQueue', dlQueue) - - async.eachLimit(dlQueue, limit, (asset, cb) => { - - fs.ensureDirSync(path.join(asset.to, '..')) - - let req = request(asset.from) - req.pause() - - req.on('response', (resp) => { - - if(resp.statusCode === 200){ - - let doHashCheck = false - const contentLength = parseInt(resp.headers['content-length']) - - if(contentLength !== asset.size){ - AssetGuard.logger.warn(`WARN: Got ${contentLength} bytes for ${asset.id}: Expected ${asset.size}`) - doHashCheck = true - - // Adjust download - this.totaldlsize -= asset.size - this.totaldlsize += contentLength - } - - let writeStream = fs.createWriteStream(asset.to) - writeStream.on('close', () => { - if(dlTracker.callback != null){ - dlTracker.callback.apply(dlTracker, [asset, self]) - } - - if(doHashCheck){ - const v = AssetGuard._validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash) - if(v){ - AssetGuard.logger.warn(`Hashes match for ${asset.id}, byte mismatch is an issue in the distro index.`) - } else { - AssetGuard.logger.error(`Hashes do not match, ${asset.id} may be corrupted.`) - } - } - - cb() - }) - req.pipe(writeStream) - req.resume() - - } else { - - req.abort() - AssetGuard.logger.error(`Failed to download ${asset.id}(${typeof asset.from === 'object' ? asset.from.url : asset.from}). Response code ${resp.statusCode}`) - self.progress += asset.size*1 - self.emit('progress', 'download', self.progress, self.totaldlsize) - cb() - - } - - }) - - req.on('error', (err) => { - self.emit('error', 'download', err) - }) - - req.on('data', (chunk) => { - self.progress += chunk.length - self.emit('progress', 'download', self.progress, self.totaldlsize) - }) - - }, (err) => { - - if(err){ - AssetGuard.logger.warn('An item in ' + identifier + ' failed to process') - } else { - AssetGuard.logger.info('All ' + identifier + ' have been processed successfully') - } - - //self.totaldlsize -= dlTracker.dlsize - //self.progress -= dlTracker.dlsize - self[identifier] = new DLTracker([], 0) - - if(self.progress >= self.totaldlsize) { - if(self.extractQueue.length > 0){ - self.emit('progress', 'extract', 1, 1) - //self.emit('extracting') - AssetGuard._extractPackXZ(self.extractQueue, self.javaexec).then(() => { - self.extractQueue = [] - self.emit('complete', 'download') - }) - } else { - self.emit('complete', 'download') - } - } - - }) - - return true - - } else { - return false - } - } - - /** - * This function will initiate the download processed for the specified identifiers. If no argument is - * given, all identifiers will be initiated. Note that in order for files to be processed you need to run - * the processing function corresponding to that identifier. If you run this function without processing - * the files, it is likely nothing will be enqueued in the object and processing will complete - * immediately. Once all downloads are complete, this function will fire the 'complete' event on the - * global object instance. - * - * @param {Array.<{id: string, limit: number}>} identifiers Optional. The identifiers to process and corresponding parallel async task limit. - */ - processDlQueues(identifiers = [{id:'assets', limit:20}, {id:'libraries', limit:5}, {id:'files', limit:5}, {id:'forge', limit:5}]){ - return new Promise((resolve, reject) => { - let shouldFire = true - - // Assign dltracking variables. - this.totaldlsize = 0 - this.progress = 0 - - for(let iden of identifiers){ - this.totaldlsize += this[iden.id].dlsize - } - - this.once('complete', (data) => { - resolve() - }) - - for(let iden of identifiers){ - let r = this.startAsyncProcess(iden.id, iden.limit) - if(r) shouldFire = false - } - - if(shouldFire){ - this.emit('complete', 'download') - } - }) - } - - async validateEverything(serverid, dev = false){ - - try { - if(!ConfigManager.isLoaded()){ - ConfigManager.load() - } - DistroManager.setDevMode(dev) - const dI = await DistroManager.pullLocal() - - const server = dI.getServer(serverid) - - // Validate Everything - - await this.validateDistribution(server) - this.emit('validate', 'distribution') - const versionData = await this.loadVersionData(server.getMinecraftVersion()) - this.emit('validate', 'version') - await this.validateAssets(versionData) - this.emit('validate', 'assets') - await this.validateLibraries(versionData) - this.emit('validate', 'libraries') - await this.validateMiscellaneous(versionData) - this.emit('validate', 'files') - await this.processDlQueues() - //this.emit('complete', 'download') - const forgeData = await this.loadForgeData(server) - - return { - versionData, - forgeData - } - - } catch (err){ - return { - versionData: null, - forgeData: null, - error: err - } - } - - - } - - // #endregion - -} - -module.exports = { - Util, - AssetGuard, - JavaGuard, - Asset, - Library -} diff --git a/app/assets/js/authmanager.js b/app/assets/js/authmanager.js deleted file mode 100644 index 3f431440..00000000 --- a/app/assets/js/authmanager.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * AuthManager - * - * This module aims to abstract login procedures. Results from Mojang's REST api - * are retrieved through our Mojang module. These results are processed and stored, - * if applicable, in the config using the ConfigManager. All login procedures should - * be made through this module. - * - * @module authmanager - */ -// Requirements -const ConfigManager = require('./configmanager') -const { LoggerUtil } = require('helios-core') -const { RestResponseStatus } = require('helios-core/common') -const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang') -const { MicrosoftAuth, microsoftErrorDisplayable, MicrosoftErrorCode } = require('helios-core/microsoft') -const { AZURE_CLIENT_ID } = require('./ipcconstants') - -const log = LoggerUtil.getLogger('AuthManager') - -// Functions - -/** - * Add a Mojang account. This will authenticate the given credentials with Mojang's - * authserver. The resultant data will be stored as an auth account in the - * configuration database. - * - * @param {string} username The account username (email if migrated). - * @param {string} password The account password. - * @returns {Promise.} Promise which resolves the resolved authenticated account object. - */ -exports.addMojangAccount = async function(username, password) { - try { - const response = await MojangRestAPI.authenticate(username, password, ConfigManager.getClientToken()) - console.log(response) - if(response.responseStatus === RestResponseStatus.SUCCESS) { - - const session = response.data - if(session.selectedProfile != null){ - const ret = ConfigManager.addMojangAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name) - if(ConfigManager.getClientToken() == null){ - ConfigManager.setClientToken(session.clientToken) - } - ConfigManager.save() - return ret - } else { - return Promise.reject(mojangErrorDisplayable(MojangErrorCode.ERROR_NOT_PAID)) - } - - } else { - return Promise.reject(mojangErrorDisplayable(response.mojangErrorCode)) - } - - } catch (err){ - log.error(err) - return Promise.reject(mojangErrorDisplayable(MojangErrorCode.UNKNOWN)) - } -} - -const AUTH_MODE = { FULL: 0, MS_REFRESH: 1, MC_REFRESH: 2 } - -/** - * Perform the full MS Auth flow in a given mode. - * - * AUTH_MODE.FULL = Full authorization for a new account. - * AUTH_MODE.MS_REFRESH = Full refresh authorization. - * AUTH_MODE.MC_REFRESH = Refresh of the MC token, reusing the MS token. - * - * @param {string} entryCode FULL-AuthCode. MS_REFRESH=refreshToken, MC_REFRESH=accessToken - * @param {*} authMode The auth mode. - * @returns An object with all auth data. AccessToken object will be null when mode is MC_REFRESH. - */ -async function fullMicrosoftAuthFlow(entryCode, authMode) { - try { - - let accessTokenRaw - let accessToken - if(authMode !== AUTH_MODE.MC_REFRESH) { - const accessTokenResponse = await MicrosoftAuth.getAccessToken(entryCode, authMode === AUTH_MODE.MS_REFRESH, AZURE_CLIENT_ID) - if(accessTokenResponse.responseStatus === RestResponseStatus.ERROR) { - return Promise.reject(microsoftErrorDisplayable(accessTokenResponse.microsoftErrorCode)) - } - accessToken = accessTokenResponse.data - accessTokenRaw = accessToken.access_token - } else { - accessTokenRaw = entryCode - } - - const xblResponse = await MicrosoftAuth.getXBLToken(accessTokenRaw) - if(xblResponse.responseStatus === RestResponseStatus.ERROR) { - return Promise.reject(microsoftErrorDisplayable(xblResponse.microsoftErrorCode)) - } - const xstsResonse = await MicrosoftAuth.getXSTSToken(xblResponse.data) - if(xstsResonse.responseStatus === RestResponseStatus.ERROR) { - return Promise.reject(microsoftErrorDisplayable(xstsResonse.microsoftErrorCode)) - } - const mcTokenResponse = await MicrosoftAuth.getMCAccessToken(xstsResonse.data) - if(mcTokenResponse.responseStatus === RestResponseStatus.ERROR) { - return Promise.reject(microsoftErrorDisplayable(mcTokenResponse.microsoftErrorCode)) - } - const mcProfileResponse = await MicrosoftAuth.getMCProfile(mcTokenResponse.data.access_token) - if(mcProfileResponse.responseStatus === RestResponseStatus.ERROR) { - return Promise.reject(microsoftErrorDisplayable(mcProfileResponse.microsoftErrorCode)) - } - return { - accessToken, - accessTokenRaw, - xbl: xblResponse.data, - xsts: xstsResonse.data, - mcToken: mcTokenResponse.data, - mcProfile: mcProfileResponse.data - } - } catch(err) { - log.error(err) - return Promise.reject(microsoftErrorDisplayable(MicrosoftErrorCode.UNKNOWN)) - } -} - -/** - * Calculate the expiry date. Advance the expiry time by 10 seconds - * to reduce the liklihood of working with an expired token. - * - * @param {number} nowMs Current time milliseconds. - * @param {number} epiresInS Expires in (seconds) - * @returns - */ -function calculateExpiryDate(nowMs, epiresInS) { - return nowMs + ((epiresInS-10)*1000) -} - -/** - * Add a Microsoft account. This will pass the provided auth code to Mojang's OAuth2.0 flow. - * The resultant data will be stored as an auth account in the configuration database. - * - * @param {string} authCode The authCode obtained from microsoft. - * @returns {Promise.} Promise which resolves the resolved authenticated account object. - */ -exports.addMicrosoftAccount = async function(authCode) { - - const fullAuth = await fullMicrosoftAuthFlow(authCode, AUTH_MODE.FULL) - - // Advance expiry by 10 seconds to avoid close calls. - const now = new Date().getTime() - - const ret = ConfigManager.addMicrosoftAuthAccount( - fullAuth.mcProfile.id, - fullAuth.mcToken.access_token, - fullAuth.mcProfile.name, - calculateExpiryDate(now, fullAuth.mcToken.expires_in), - fullAuth.accessToken.access_token, - fullAuth.accessToken.refresh_token, - calculateExpiryDate(now, fullAuth.accessToken.expires_in) - ) - ConfigManager.save() - - return ret -} - -/** - * Remove a Mojang account. This will invalidate the access token associated - * with the account and then remove it from the database. - * - * @param {string} uuid The UUID of the account to be removed. - * @returns {Promise.} Promise which resolves to void when the action is complete. - */ -exports.removeMojangAccount = async function(uuid){ - try { - const authAcc = ConfigManager.getAuthAccount(uuid) - const response = await MojangRestAPI.invalidate(authAcc.accessToken, ConfigManager.getClientToken()) - if(response.responseStatus === RestResponseStatus.SUCCESS) { - ConfigManager.removeAuthAccount(uuid) - ConfigManager.save() - return Promise.resolve() - } else { - log.error('Error while removing account', response.error) - return Promise.reject(response.error) - } - } catch (err){ - log.error('Error while removing account', err) - return Promise.reject(err) - } -} - -/** - * Remove a Microsoft account. It is expected that the caller will invoke the OAuth logout - * through the ipc renderer. - * - * @param {string} uuid The UUID of the account to be removed. - * @returns {Promise.} Promise which resolves to void when the action is complete. - */ -exports.removeMicrosoftAccount = async function(uuid){ - try { - ConfigManager.removeAuthAccount(uuid) - ConfigManager.save() - return Promise.resolve() - } catch (err){ - log.error('Error while removing account', err) - return Promise.reject(err) - } -} - -/** - * Validate the selected account with Mojang's authserver. If the account is not valid, - * we will attempt to refresh the access token and update that value. If that fails, a - * new login will be required. - * - * @returns {Promise.} Promise which resolves to true if the access token is valid, - * otherwise false. - */ -async function validateSelectedMojangAccount(){ - const current = ConfigManager.getSelectedAccount() - const response = await MojangRestAPI.validate(current.accessToken, ConfigManager.getClientToken()) - - if(response.responseStatus === RestResponseStatus.SUCCESS) { - const isValid = response.data - if(!isValid){ - const refreshResponse = await MojangRestAPI.refresh(current.accessToken, ConfigManager.getClientToken()) - if(refreshResponse.responseStatus === RestResponseStatus.SUCCESS) { - const session = refreshResponse.data - ConfigManager.updateMojangAuthAccount(current.uuid, session.accessToken) - ConfigManager.save() - } else { - log.error('Error while validating selected profile:', refreshResponse.error) - log.info('Account access token is invalid.') - return false - } - log.info('Account access token validated.') - return true - } else { - log.info('Account access token validated.') - return true - } - } - -} - -/** - * Validate the selected account with Microsoft's authserver. If the account is not valid, - * we will attempt to refresh the access token and update that value. If that fails, a - * new login will be required. - * - * @returns {Promise.} Promise which resolves to true if the access token is valid, - * otherwise false. - */ -async function validateSelectedMicrosoftAccount(){ - const current = ConfigManager.getSelectedAccount() - const now = new Date().getTime() - const mcExpiresAt = current.expiresAt - const mcExpired = now >= mcExpiresAt - - if(!mcExpired) { - return true - } - - // MC token expired. Check MS token. - - const msExpiresAt = current.microsoft.expires_at - const msExpired = now >= msExpiresAt - - if(msExpired) { - // MS expired, do full refresh. - try { - const res = await fullMicrosoftAuthFlow(current.microsoft.refresh_token, AUTH_MODE.MS_REFRESH) - - ConfigManager.updateMicrosoftAuthAccount( - current.uuid, - res.mcToken.access_token, - res.accessToken.access_token, - res.accessToken.refresh_token, - calculateExpiryDate(now, res.accessToken.expires_in), - calculateExpiryDate(now, res.mcToken.expires_in) - ) - ConfigManager.save() - return true - } catch(err) { - return false - } - } else { - // Only MC expired, use existing MS token. - try { - const res = await fullMicrosoftAuthFlow(current.microsoft.access_token, AUTH_MODE.MC_REFRESH) - - ConfigManager.updateMicrosoftAuthAccount( - current.uuid, - res.mcToken.access_token, - current.microsoft.access_token, - current.microsoft.refresh_token, - current.microsoft.expires_at, - calculateExpiryDate(now, res.mcToken.expires_in) - ) - ConfigManager.save() - return true - } - catch(err) { - return false - } - } -} - -/** - * Validate the selected auth account. - * - * @returns {Promise.} Promise which resolves to true if the access token is valid, - * otherwise false. - */ -exports.validateSelected = async function(){ - const current = ConfigManager.getSelectedAccount() - - if(current.type === 'microsoft') { - return await validateSelectedMicrosoftAccount() - } else { - return await validateSelectedMojangAccount() - } - -} diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js deleted file mode 100644 index c3f74897..00000000 --- a/app/assets/js/configmanager.js +++ /dev/null @@ -1,809 +0,0 @@ -const fs = require('fs-extra') -const { LoggerUtil } = require('helios-core') -const os = require('os') -const path = require('path') - -const logger = LoggerUtil.getLogger('ConfigManager') - -const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME) -// TODO change -const dataPath = path.join(sysRoot, '.helioslauncher') - -// Forked processes do not have access to electron, so we have this workaround. -const launcherDir = process.env.CONFIG_DIRECT_PATH || require('@electron/remote').app.getPath('userData') - -/** - * Retrieve the absolute path of the launcher directory. - * - * @returns {string} The absolute path of the launcher directory. - */ -exports.getLauncherDirectory = function(){ - return launcherDir -} - -/** - * Get the launcher's data directory. This is where all files related - * to game launch are installed (common, instances, java, etc). - * - * @returns {string} The absolute path of the launcher's data directory. - */ -exports.getDataDirectory = function(def = false){ - return !def ? config.settings.launcher.dataDirectory : DEFAULT_CONFIG.settings.launcher.dataDirectory -} - -/** - * Set the new data directory. - * - * @param {string} dataDirectory The new data directory. - */ -exports.setDataDirectory = function(dataDirectory){ - config.settings.launcher.dataDirectory = dataDirectory -} - -const configPath = path.join(exports.getLauncherDirectory(), 'config.json') -const configPathLEGACY = path.join(dataPath, 'config.json') -const firstLaunch = !fs.existsSync(configPath) && !fs.existsSync(configPathLEGACY) - -exports.getAbsoluteMinRAM = function(){ - const mem = os.totalmem() - return mem >= 6000000000 ? 3 : 2 -} - -exports.getAbsoluteMaxRAM = function(){ - const mem = os.totalmem() - const gT16 = mem-16000000000 - return Math.floor((mem-1000000000-(gT16 > 0 ? (Number.parseInt(gT16/8) + 16000000000/4) : mem/4))/1000000000) -} - -function resolveMaxRAM(){ - const mem = os.totalmem() - return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G') -} - -function resolveMinRAM(){ - return resolveMaxRAM() -} - -/** - * TODO Copy pasted, should be in a utility file. - * - * Returns true if the actual version is greater than - * or equal to the desired version. - * - * @param {string} desired The desired version. - * @param {string} actual The actual version. - */ -function mcVersionAtLeast(desired, actual){ - const des = desired.split('.') - const act = actual.split('.') - - for(let i=0; i= parseInt(des[i]))){ - return false - } - } - return true -} - -/** - * Three types of values: - * Static = Explicitly declared. - * Dynamic = Calculated by a private function. - * Resolved = Resolved externally, defaults to null. - */ -const DEFAULT_CONFIG = { - settings: { - game: { - resWidth: 1280, - resHeight: 720, - fullscreen: false, - autoConnect: true, - launchDetached: true - }, - launcher: { - allowPrerelease: false, - dataDirectory: dataPath - } - }, - newsCache: { - date: null, - content: null, - dismissed: false - }, - clientToken: null, - selectedServer: null, // Resolved - selectedAccount: null, - authenticationDatabase: {}, - modConfigurations: [], - javaConfig: {} -} - -let config = null - -// Persistance Utility Functions - -/** - * Save the current configuration to a file. - */ -exports.save = function(){ - fs.writeFileSync(configPath, JSON.stringify(config, null, 4), 'UTF-8') -} - -/** - * Load the configuration into memory. If a configuration file exists, - * that will be read and saved. Otherwise, a default configuration will - * be generated. Note that "resolved" values default to null and will - * need to be externally assigned. - */ -exports.load = function(){ - let doLoad = true - - if(!fs.existsSync(configPath)){ - // Create all parent directories. - fs.ensureDirSync(path.join(configPath, '..')) - if(fs.existsSync(configPathLEGACY)){ - fs.moveSync(configPathLEGACY, configPath) - } else { - doLoad = false - config = DEFAULT_CONFIG - exports.save() - } - } - if(doLoad){ - let doValidate = false - try { - config = JSON.parse(fs.readFileSync(configPath, 'UTF-8')) - doValidate = true - } catch (err){ - logger.error(err) - logger.info('Configuration file contains malformed JSON or is corrupt.') - logger.info('Generating a new configuration file.') - fs.ensureDirSync(path.join(configPath, '..')) - config = DEFAULT_CONFIG - exports.save() - } - if(doValidate){ - config = validateKeySet(DEFAULT_CONFIG, config) - exports.save() - } - } - logger.info('Successfully Loaded') -} - -/** - * @returns {boolean} Whether or not the manager has been loaded. - */ -exports.isLoaded = function(){ - return config != null -} - -/** - * Validate that the destination object has at least every field - * present in the source object. Assign a default value otherwise. - * - * @param {Object} srcObj The source object to reference against. - * @param {Object} destObj The destination object. - * @returns {Object} A validated destination object. - */ -function validateKeySet(srcObj, destObj){ - if(srcObj == null){ - srcObj = {} - } - const validationBlacklist = ['authenticationDatabase', 'javaConfig'] - const keys = Object.keys(srcObj) - for(let i=0; i} An array of each stored authenticated account. - */ -exports.getAuthAccounts = function(){ - return config.authenticationDatabase -} - -/** - * Returns the authenticated account with the given uuid. Value may - * be null. - * - * @param {string} uuid The uuid of the authenticated account. - * @returns {Object} The authenticated account with the given uuid. - */ -exports.getAuthAccount = function(uuid){ - return config.authenticationDatabase[uuid] -} - -/** - * Update the access token of an authenticated mojang account. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The new Access Token. - * - * @returns {Object} The authenticated account object created by this action. - */ -exports.updateMojangAuthAccount = function(uuid, accessToken){ - config.authenticationDatabase[uuid].accessToken = accessToken - config.authenticationDatabase[uuid].type = 'mojang' // For gradual conversion. - return config.authenticationDatabase[uuid] -} - -/** - * Adds an authenticated mojang account to the database to be stored. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The accessToken of the authenticated account. - * @param {string} username The username (usually email) of the authenticated account. - * @param {string} displayName The in game name of the authenticated account. - * - * @returns {Object} The authenticated account object created by this action. - */ -exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName){ - config.selectedAccount = uuid - config.authenticationDatabase[uuid] = { - type: 'mojang', - accessToken, - username: username.trim(), - uuid: uuid.trim(), - displayName: displayName.trim() - } - return config.authenticationDatabase[uuid] -} - -/** - * Update the tokens of an authenticated microsoft account. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The new Access Token. - * @param {string} msAccessToken The new Microsoft Access Token - * @param {string} msRefreshToken The new Microsoft Refresh Token - * @param {date} msExpires The date when the microsoft access token expires - * @param {date} mcExpires The date when the mojang access token expires - * - * @returns {Object} The authenticated account object created by this action. - */ -exports.updateMicrosoftAuthAccount = function(uuid, accessToken, msAccessToken, msRefreshToken, msExpires, mcExpires) { - config.authenticationDatabase[uuid].accessToken = accessToken - config.authenticationDatabase[uuid].expiresAt = mcExpires - config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken - config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken - config.authenticationDatabase[uuid].microsoft.expires_at = msExpires - return config.authenticationDatabase[uuid] -} - -/** - * Adds an authenticated microsoft account to the database to be stored. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The accessToken of the authenticated account. - * @param {string} name The in game name of the authenticated account. - * @param {date} mcExpires The date when the mojang access token expires - * @param {string} msAccessToken The microsoft access token - * @param {string} msRefreshToken The microsoft refresh token - * @param {date} msExpires The date when the microsoft access token expires - * - * @returns {Object} The authenticated account object created by this action. - */ -exports.addMicrosoftAuthAccount = function(uuid, accessToken, name, mcExpires, msAccessToken, msRefreshToken, msExpires) { - config.selectedAccount = uuid - config.authenticationDatabase[uuid] = { - type: 'microsoft', - accessToken, - username: name.trim(), - uuid: uuid.trim(), - displayName: name.trim(), - expiresAt: mcExpires, - microsoft: { - access_token: msAccessToken, - refresh_token: msRefreshToken, - expires_at: msExpires - } - } - return config.authenticationDatabase[uuid] -} - -/** - * Remove an authenticated account from the database. If the account - * was also the selected account, a new one will be selected. If there - * are no accounts, the selected account will be null. - * - * @param {string} uuid The uuid of the authenticated account. - * - * @returns {boolean} True if the account was removed, false if it never existed. - */ -exports.removeAuthAccount = function(uuid){ - if(config.authenticationDatabase[uuid] != null){ - delete config.authenticationDatabase[uuid] - if(config.selectedAccount === uuid){ - const keys = Object.keys(config.authenticationDatabase) - if(keys.length > 0){ - config.selectedAccount = keys[0] - } else { - config.selectedAccount = null - config.clientToken = null - } - } - return true - } - return false -} - -/** - * Get the currently selected authenticated account. - * - * @returns {Object} The selected authenticated account. - */ -exports.getSelectedAccount = function(){ - return config.authenticationDatabase[config.selectedAccount] -} - -/** - * Set the selected authenticated account. - * - * @param {string} uuid The UUID of the account which is to be set - * as the selected account. - * - * @returns {Object} The selected authenticated account. - */ -exports.setSelectedAccount = function(uuid){ - const authAcc = config.authenticationDatabase[uuid] - if(authAcc != null) { - config.selectedAccount = uuid - } - return authAcc -} - -/** - * Get an array of each mod configuration currently stored. - * - * @returns {Array.} An array of each stored mod configuration. - */ -exports.getModConfigurations = function(){ - return config.modConfigurations -} - -/** - * Set the array of stored mod configurations. - * - * @param {Array.} configurations An array of mod configurations. - */ -exports.setModConfigurations = function(configurations){ - config.modConfigurations = configurations -} - -/** - * Get the mod configuration for a specific server. - * - * @param {string} serverid The id of the server. - * @returns {Object} The mod configuration for the given server. - */ -exports.getModConfiguration = function(serverid){ - const cfgs = config.modConfigurations - for(let i=0; i} An array of the additional arguments for JVM initialization. - */ -exports.getJVMOptions = function(serverid){ - return config.javaConfig[serverid].jvmOptions -} - -/** - * Set the additional arguments for JVM initialization. Required arguments, - * such as memory allocation, will be dynamically resolved and should not be - * included in this value. - * - * @param {string} serverid The server id. - * @param {Array.} jvmOptions An array of the new additional arguments for JVM - * initialization. - */ -exports.setJVMOptions = function(serverid, jvmOptions){ - config.javaConfig[serverid].jvmOptions = jvmOptions -} - -// Game Settings - -/** - * Retrieve the width of the game window. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {number} The width of the game window. - */ -exports.getGameWidth = function(def = false){ - return !def ? config.settings.game.resWidth : DEFAULT_CONFIG.settings.game.resWidth -} - -/** - * Set the width of the game window. - * - * @param {number} resWidth The new width of the game window. - */ -exports.setGameWidth = function(resWidth){ - config.settings.game.resWidth = Number.parseInt(resWidth) -} - -/** - * Validate a potential new width value. - * - * @param {number} resWidth The width value to validate. - * @returns {boolean} Whether or not the value is valid. - */ -exports.validateGameWidth = function(resWidth){ - const nVal = Number.parseInt(resWidth) - return Number.isInteger(nVal) && nVal >= 0 -} - -/** - * Retrieve the height of the game window. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {number} The height of the game window. - */ -exports.getGameHeight = function(def = false){ - return !def ? config.settings.game.resHeight : DEFAULT_CONFIG.settings.game.resHeight -} - -/** - * Set the height of the game window. - * - * @param {number} resHeight The new height of the game window. - */ -exports.setGameHeight = function(resHeight){ - config.settings.game.resHeight = Number.parseInt(resHeight) -} - -/** - * Validate a potential new height value. - * - * @param {number} resHeight The height value to validate. - * @returns {boolean} Whether or not the value is valid. - */ -exports.validateGameHeight = function(resHeight){ - const nVal = Number.parseInt(resHeight) - return Number.isInteger(nVal) && nVal >= 0 -} - -/** - * Check if the game should be launched in fullscreen mode. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the game is set to launch in fullscreen mode. - */ -exports.getFullscreen = function(def = false){ - return !def ? config.settings.game.fullscreen : DEFAULT_CONFIG.settings.game.fullscreen -} - -/** - * Change the status of if the game should be launched in fullscreen mode. - * - * @param {boolean} fullscreen Whether or not the game should launch in fullscreen mode. - */ -exports.setFullscreen = function(fullscreen){ - config.settings.game.fullscreen = fullscreen -} - -/** - * Check if the game should auto connect to servers. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the game should auto connect to servers. - */ -exports.getAutoConnect = function(def = false){ - return !def ? config.settings.game.autoConnect : DEFAULT_CONFIG.settings.game.autoConnect -} - -/** - * Change the status of whether or not the game should auto connect to servers. - * - * @param {boolean} autoConnect Whether or not the game should auto connect to servers. - */ -exports.setAutoConnect = function(autoConnect){ - config.settings.game.autoConnect = autoConnect -} - -/** - * Check if the game should launch as a detached process. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the game will launch as a detached process. - */ -exports.getLaunchDetached = function(def = false){ - return !def ? config.settings.game.launchDetached : DEFAULT_CONFIG.settings.game.launchDetached -} - -/** - * Change the status of whether or not the game should launch as a detached process. - * - * @param {boolean} launchDetached Whether or not the game should launch as a detached process. - */ -exports.setLaunchDetached = function(launchDetached){ - config.settings.game.launchDetached = launchDetached -} - -// Launcher Settings - -/** - * Check if the launcher should download prerelease versions. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the launcher should download prerelease versions. - */ -exports.getAllowPrerelease = function(def = false){ - return !def ? config.settings.launcher.allowPrerelease : DEFAULT_CONFIG.settings.launcher.allowPrerelease -} - -/** - * Change the status of Whether or not the launcher should download prerelease versions. - * - * @param {boolean} launchDetached Whether or not the launcher should download prerelease versions. - */ -exports.setAllowPrerelease = function(allowPrerelease){ - config.settings.launcher.allowPrerelease = allowPrerelease -} \ No newline at end of file diff --git a/app/assets/js/discordwrapper.js b/app/assets/js/discordwrapper.js deleted file mode 100644 index 1e44e867..00000000 --- a/app/assets/js/discordwrapper.js +++ /dev/null @@ -1,50 +0,0 @@ -// Work in progress -const { LoggerUtil } = require('helios-core') - -const logger = LoggerUtil.getLogger('DiscordWrapper') - -const { Client } = require('discord-rpc-patch') - -let client -let activity - -exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting for Client..'){ - client = new Client({ transport: 'ipc' }) - - activity = { - details: initialDetails, - state: 'Server: ' + servSettings.shortId, - largeImageKey: servSettings.largeImageKey, - largeImageText: servSettings.largeImageText, - smallImageKey: genSettings.smallImageKey, - smallImageText: genSettings.smallImageText, - startTimestamp: new Date().getTime(), - instance: false - } - - client.on('ready', () => { - logger.info('Discord RPC Connected') - client.setActivity(activity) - }) - - client.login({clientId: genSettings.clientId}).catch(error => { - if(error.message.includes('ENOENT')) { - logger.info('Unable to initialize Discord Rich Presence, no client detected.') - } else { - logger.info('Unable to initialize Discord Rich Presence: ' + error.message, error) - } - }) -} - -exports.updateDetails = function(details){ - activity.details = details - client.setActivity(activity) -} - -exports.shutdownRPC = function(){ - if(!client) return - client.clearActivity() - client.destroy() - client = null - activity = null -} \ No newline at end of file diff --git a/app/assets/js/distromanager.js b/app/assets/js/distromanager.js deleted file mode 100644 index 4fcc6852..00000000 --- a/app/assets/js/distromanager.js +++ /dev/null @@ -1,621 +0,0 @@ -const fs = require('fs') -const path = require('path') -const request = require('request') -const { LoggerUtil } = require('helios-core') - -const ConfigManager = require('./configmanager') - -const logger = LoggerUtil.getLogger('DistroManager') - -/** - * Represents the download information - * for a specific module. - */ -class Artifact { - - /** - * Parse a JSON object into an Artifact. - * - * @param {Object} json A JSON object representing an Artifact. - * - * @returns {Artifact} The parsed Artifact. - */ - static fromJSON(json){ - return Object.assign(new Artifact(), json) - } - - /** - * Get the MD5 hash of the artifact. This value may - * be undefined for artifacts which are not to be - * validated and updated. - * - * @returns {string} The MD5 hash of the Artifact or undefined. - */ - getHash(){ - return this.MD5 - } - - /** - * @returns {number} The download size of the artifact. - */ - getSize(){ - return this.size - } - - /** - * @returns {string} The download url of the artifact. - */ - getURL(){ - return this.url - } - - /** - * @returns {string} The artifact's destination path. - */ - getPath(){ - return this.path - } - -} -exports.Artifact - -/** - * Represents a the requirement status - * of a module. - */ -class Required { - - /** - * Parse a JSON object into a Required object. - * - * @param {Object} json A JSON object representing a Required object. - * - * @returns {Required} The parsed Required object. - */ - static fromJSON(json){ - if(json == null){ - return new Required(true, true) - } else { - return new Required(json.value == null ? true : json.value, json.def == null ? true : json.def) - } - } - - constructor(value, def){ - this.value = value - this.default = def - } - - /** - * Get the default value for a required object. If a module - * is not required, this value determines whether or not - * it is enabled by default. - * - * @returns {boolean} The default enabled value. - */ - isDefault(){ - return this.default - } - - /** - * @returns {boolean} Whether or not the module is required. - */ - isRequired(){ - return this.value - } - -} -exports.Required - -/** - * Represents a module. - */ -class Module { - - /** - * Parse a JSON object into a Module. - * - * @param {Object} json A JSON object representing a Module. - * @param {string} serverid The ID of the server to which this module belongs. - * - * @returns {Module} The parsed Module. - */ - static fromJSON(json, serverid){ - return new Module(json.id, json.name, json.type, json.classpath, json.required, json.artifact, json.subModules, serverid) - } - - /** - * Resolve the default extension for a specific module type. - * - * @param {string} type The type of the module. - * - * @return {string} The default extension for the given type. - */ - static _resolveDefaultExtension(type){ - switch (type) { - case exports.Types.Library: - case exports.Types.ForgeHosted: - case exports.Types.LiteLoader: - case exports.Types.ForgeMod: - return 'jar' - case exports.Types.LiteMod: - return 'litemod' - case exports.Types.File: - default: - return 'jar' // There is no default extension really. - } - } - - constructor(id, name, type, classpath, required, artifact, subModules, serverid) { - this.identifier = id - this.type = type - this.classpath = classpath - this._resolveMetaData() - this.name = name - this.required = Required.fromJSON(required) - this.artifact = Artifact.fromJSON(artifact) - this._resolveArtifactPath(artifact.path, serverid) - this._resolveSubModules(subModules, serverid) - } - - _resolveMetaData(){ - try { - - const m0 = this.identifier.split('@') - - this.artifactExt = m0[1] || Module._resolveDefaultExtension(this.type) - - const m1 = m0[0].split(':') - - this.artifactClassifier = m1[3] || undefined - this.artifactVersion = m1[2] || '???' - this.artifactID = m1[1] || '???' - this.artifactGroup = m1[0] || '???' - - } catch (err) { - // Improper identifier - logger.error('Improper ID for module', this.identifier, err) - } - } - - _resolveArtifactPath(artifactPath, serverid){ - const pth = artifactPath == null ? path.join(...this.getGroup().split('.'), this.getID(), this.getVersion(), `${this.getID()}-${this.getVersion()}${this.artifactClassifier != undefined ? `-${this.artifactClassifier}` : ''}.${this.getExtension()}`) : artifactPath - - switch (this.type){ - case exports.Types.Library: - case exports.Types.ForgeHosted: - case exports.Types.LiteLoader: - this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'libraries', pth) - break - case exports.Types.ForgeMod: - case exports.Types.LiteMod: - this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'modstore', pth) - break - case exports.Types.VersionManifest: - this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'versions', this.getIdentifier(), `${this.getIdentifier()}.json`) - break - case exports.Types.File: - default: - this.artifact.path = path.join(ConfigManager.getInstanceDirectory(), serverid, pth) - break - } - - } - - _resolveSubModules(json, serverid){ - const arr = [] - if(json != null){ - for(let sm of json){ - arr.push(Module.fromJSON(sm, serverid)) - } - } - this.subModules = arr.length > 0 ? arr : null - } - - /** - * @returns {string} The full, unparsed module identifier. - */ - getIdentifier(){ - return this.identifier - } - - /** - * @returns {string} The name of the module. - */ - getName(){ - return this.name - } - - /** - * @returns {Required} The required object declared by this module. - */ - getRequired(){ - return this.required - } - - /** - * @returns {Artifact} The artifact declared by this module. - */ - getArtifact(){ - return this.artifact - } - - /** - * @returns {string} The maven identifier of this module's artifact. - */ - getID(){ - return this.artifactID - } - - /** - * @returns {string} The maven group of this module's artifact. - */ - getGroup(){ - return this.artifactGroup - } - - /** - * @returns {string} The identifier without he version or extension. - */ - getVersionlessID(){ - return this.getGroup() + ':' + this.getID() - } - - /** - * @returns {string} The identifier without the extension. - */ - getExtensionlessID(){ - return this.getIdentifier().split('@')[0] - } - - /** - * @returns {string} The version of this module's artifact. - */ - getVersion(){ - return this.artifactVersion - } - - /** - * @returns {string} The classifier of this module's artifact - */ - getClassifier(){ - return this.artifactClassifier - } - - /** - * @returns {string} The extension of this module's artifact. - */ - getExtension(){ - return this.artifactExt - } - - /** - * @returns {boolean} Whether or not this module has sub modules. - */ - hasSubModules(){ - return this.subModules != null - } - - /** - * @returns {Array.} An array of sub modules. - */ - getSubModules(){ - return this.subModules - } - - /** - * @returns {string} The type of the module. - */ - getType(){ - return this.type - } - - /** - * @returns {boolean} Whether or not this library should be on the classpath. - */ - getClasspath(){ - return this.classpath ?? true - } - -} -exports.Module - -/** - * Represents a server configuration. - */ -class Server { - - /** - * Parse a JSON object into a Server. - * - * @param {Object} json A JSON object representing a Server. - * - * @returns {Server} The parsed Server object. - */ - static fromJSON(json){ - - const mdls = json.modules - json.modules = [] - - const serv = Object.assign(new Server(), json) - serv._resolveModules(mdls) - - return serv - } - - _resolveModules(json){ - const arr = [] - for(let m of json){ - arr.push(Module.fromJSON(m, this.getID())) - } - this.modules = arr - } - - /** - * @returns {string} The ID of the server. - */ - getID(){ - return this.id - } - - /** - * @returns {string} The name of the server. - */ - getName(){ - return this.name - } - - /** - * @returns {string} The description of the server. - */ - getDescription(){ - return this.description - } - - /** - * @returns {string} The URL of the server's icon. - */ - getIcon(){ - return this.icon - } - - /** - * @returns {string} The version of the server configuration. - */ - getVersion(){ - return this.version - } - - /** - * @returns {string} The IP address of the server. - */ - getAddress(){ - return this.address - } - - /** - * @returns {string} The minecraft version of the server. - */ - getMinecraftVersion(){ - return this.minecraftVersion - } - - /** - * @returns {boolean} Whether or not this server is the main - * server. The main server is selected by the launcher when - * no valid server is selected. - */ - isMainServer(){ - return this.mainServer - } - - /** - * @returns {boolean} Whether or not the server is autoconnect. - * by default. - */ - isAutoConnect(){ - return this.autoconnect - } - - /** - * @returns {Array.} An array of modules for this server. - */ - getModules(){ - return this.modules - } - -} -exports.Server - -/** - * Represents the Distribution Index. - */ -class DistroIndex { - - /** - * Parse a JSON object into a DistroIndex. - * - * @param {Object} json A JSON object representing a DistroIndex. - * - * @returns {DistroIndex} The parsed Server object. - */ - static fromJSON(json){ - - const servers = json.servers - json.servers = [] - - const distro = Object.assign(new DistroIndex(), json) - distro._resolveServers(servers) - distro._resolveMainServer() - - return distro - } - - _resolveServers(json){ - const arr = [] - for(let s of json){ - arr.push(Server.fromJSON(s)) - } - this.servers = arr - } - - _resolveMainServer(){ - - for(let serv of this.servers){ - if(serv.mainServer){ - this.mainServer = serv.id - return - } - } - - // If no server declares default_selected, default to the first one declared. - this.mainServer = (this.servers.length > 0) ? this.servers[0].getID() : null - } - - /** - * @returns {string} The version of the distribution index. - */ - getVersion(){ - return this.version - } - - /** - * @returns {string} The URL to the news RSS feed. - */ - getRSS(){ - return this.rss - } - - /** - * @returns {Array.} An array of declared server configurations. - */ - getServers(){ - return this.servers - } - - /** - * Get a server configuration by its ID. If it does not - * exist, null will be returned. - * - * @param {string} id The ID of the server. - * - * @returns {Server} The server configuration with the given ID or null. - */ - getServer(id){ - for(let serv of this.servers){ - if(serv.id === id){ - return serv - } - } - return null - } - - /** - * Get the main server. - * - * @returns {Server} The main server. - */ - getMainServer(){ - return this.mainServer != null ? this.getServer(this.mainServer) : null - } - -} -exports.DistroIndex - -exports.Types = { - Library: 'Library', - ForgeHosted: 'ForgeHosted', - Forge: 'Forge', // Unimplemented - LiteLoader: 'LiteLoader', - ForgeMod: 'ForgeMod', - LiteMod: 'LiteMod', - File: 'File', - VersionManifest: 'VersionManifest' -} - -let DEV_MODE = false - -const DISTRO_PATH = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json') -const DEV_PATH = path.join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json') - -let data = null - -/** - * @returns {Promise.} - */ -exports.pullRemote = function(){ - if(DEV_MODE){ - return exports.pullLocal() - } - return new Promise((resolve, reject) => { - const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json' - //const distroURL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/' - const opts = { - url: distroURL, - timeout: 2500 - } - const distroDest = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json') - request(opts, (error, resp, body) => { - if(!error){ - - try { - data = DistroIndex.fromJSON(JSON.parse(body)) - } catch (e) { - reject(e) - return - } - - fs.writeFile(distroDest, body, 'utf-8', (err) => { - if(!err){ - resolve(data) - return - } else { - reject(err) - return - } - }) - } else { - reject(error) - return - } - }) - }) -} - -/** - * @returns {Promise.} - */ -exports.pullLocal = function(){ - return new Promise((resolve, reject) => { - fs.readFile(DEV_MODE ? DEV_PATH : DISTRO_PATH, 'utf-8', (err, d) => { - if(!err){ - data = DistroIndex.fromJSON(JSON.parse(d)) - resolve(data) - return - } else { - reject(err) - return - } - }) - }) -} - -exports.setDevMode = function(value){ - if(value){ - logger.info('Developer mode enabled.') - logger.info('If you don\'t know what that means, revert immediately.') - } else { - logger.info('Developer mode disabled.') - } - DEV_MODE = value -} - -exports.isDevMode = function(){ - return DEV_MODE -} - -/** - * @returns {DistroIndex} - */ -exports.getDistribution = function(){ - return data -} \ No newline at end of file diff --git a/app/assets/js/dropinmodutil.js b/app/assets/js/dropinmodutil.js deleted file mode 100644 index f816a20f..00000000 --- a/app/assets/js/dropinmodutil.js +++ /dev/null @@ -1,238 +0,0 @@ -const fs = require('fs-extra') -const path = require('path') -const { ipcRenderer, shell } = require('electron') -const { SHELL_OPCODE } = require('./ipcconstants') - -// Group #1: File Name (without .disabled, if any) -// Group #2: File Extension (jar, zip, or litemod) -// Group #3: If it is disabled (if string 'disabled' is present) -const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/ -const DISABLED_EXT = '.disabled' - -const SHADER_REGEX = /^(.+)\.zip$/ -const SHADER_OPTION = /shaderPack=(.+)/ -const SHADER_DIR = 'shaderpacks' -const SHADER_CONFIG = 'optionsshaders.txt' - -/** - * Validate that the given directory exists. If not, it is - * created. - * - * @param {string} modsDir The path to the mods directory. - */ -exports.validateDir = function(dir) { - fs.ensureDirSync(dir) -} - -/** - * Scan for drop-in mods in both the mods folder and version - * safe mods folder. - * - * @param {string} modsDir The path to the mods directory. - * @param {string} version The minecraft version of the server configuration. - * - * @returns {{fullName: string, name: string, ext: string, disabled: boolean}[]} - * An array of objects storing metadata about each discovered mod. - */ -exports.scanForDropinMods = function(modsDir, version) { - const modsDiscovered = [] - if(fs.existsSync(modsDir)){ - let modCandidates = fs.readdirSync(modsDir) - let verCandidates = [] - const versionDir = path.join(modsDir, version) - if(fs.existsSync(versionDir)){ - verCandidates = fs.readdirSync(versionDir) - } - for(let file of modCandidates){ - const match = MOD_REGEX.exec(file) - if(match != null){ - modsDiscovered.push({ - fullName: match[0], - name: match[1], - ext: match[2], - disabled: match[3] != null - }) - } - } - for(let file of verCandidates){ - const match = MOD_REGEX.exec(file) - if(match != null){ - modsDiscovered.push({ - fullName: path.join(version, match[0]), - name: match[1], - ext: match[2], - disabled: match[3] != null - }) - } - } - } - return modsDiscovered -} - -/** - * Add dropin mods. - * - * @param {FileList} files The files to add. - * @param {string} modsDir The path to the mods directory. - */ -exports.addDropinMods = function(files, modsdir) { - - exports.validateDir(modsdir) - - for(let f of files) { - if(MOD_REGEX.exec(f.name) != null) { - fs.moveSync(f.path, path.join(modsdir, f.name)) - } - } - -} - -/** - * Delete a drop-in mod from the file system. - * - * @param {string} modsDir The path to the mods directory. - * @param {string} fullName The fullName of the discovered mod to delete. - * - * @returns {Promise.} True if the mod was deleted, otherwise false. - */ -exports.deleteDropinMod = async function(modsDir, fullName){ - - const res = await ipcRenderer.invoke(SHELL_OPCODE.TRASH_ITEM, path.join(modsDir, fullName)) - - if(!res.result) { - shell.beep() - console.error('Error deleting drop-in mod.', res.error) - return false - } - - return true -} - -/** - * Toggle a discovered mod on or off. This is achieved by either - * adding or disabling the .disabled extension to the local file. - * - * @param {string} modsDir The path to the mods directory. - * @param {string} fullName The fullName of the discovered mod to toggle. - * @param {boolean} enable Whether to toggle on or off the mod. - * - * @returns {Promise.} A promise which resolves when the mod has - * been toggled. If an IO error occurs the promise will be rejected. - */ -exports.toggleDropinMod = function(modsDir, fullName, enable){ - return new Promise((resolve, reject) => { - const oldPath = path.join(modsDir, fullName) - const newPath = path.join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT) - - fs.rename(oldPath, newPath, (err) => { - if(err){ - reject(err) - } else { - resolve() - } - }) - }) -} - -/** - * Check if a drop-in mod is enabled. - * - * @param {string} fullName The fullName of the discovered mod to toggle. - * @returns {boolean} True if the mod is enabled, otherwise false. - */ -exports.isDropinModEnabled = function(fullName){ - return !fullName.endsWith(DISABLED_EXT) -} - -/** - * Scan for shaderpacks inside the shaderpacks folder. - * - * @param {string} instanceDir The path to the server instance directory. - * - * @returns {{fullName: string, name: string}[]} - * An array of objects storing metadata about each discovered shaderpack. - */ -exports.scanForShaderpacks = function(instanceDir){ - const shaderDir = path.join(instanceDir, SHADER_DIR) - const packsDiscovered = [{ - fullName: 'OFF', - name: 'Off (Default)' - }] - if(fs.existsSync(shaderDir)){ - let modCandidates = fs.readdirSync(shaderDir) - for(let file of modCandidates){ - const match = SHADER_REGEX.exec(file) - if(match != null){ - packsDiscovered.push({ - fullName: match[0], - name: match[1] - }) - } - } - } - return packsDiscovered -} - -/** - * Read the optionsshaders.txt file to locate the current - * enabled pack. If the file does not exist, OFF is returned. - * - * @param {string} instanceDir The path to the server instance directory. - * - * @returns {string} The file name of the enabled shaderpack. - */ -exports.getEnabledShaderpack = function(instanceDir){ - exports.validateDir(instanceDir) - - const optionsShaders = path.join(instanceDir, SHADER_CONFIG) - if(fs.existsSync(optionsShaders)){ - const buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'}) - const match = SHADER_OPTION.exec(buf) - if(match != null){ - return match[1] - } else { - console.warn('WARNING: Shaderpack regex failed.') - } - } - return 'OFF' -} - -/** - * Set the enabled shaderpack. - * - * @param {string} instanceDir The path to the server instance directory. - * @param {string} pack the file name of the shaderpack. - */ -exports.setEnabledShaderpack = function(instanceDir, pack){ - exports.validateDir(instanceDir) - - const optionsShaders = path.join(instanceDir, SHADER_CONFIG) - let buf - if(fs.existsSync(optionsShaders)){ - buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'}) - buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`) - } else { - buf = `shaderPack=${pack}` - } - fs.writeFileSync(optionsShaders, buf, {encoding: 'utf-8'}) -} - -/** - * Add shaderpacks. - * - * @param {FileList} files The files to add. - * @param {string} instanceDir The path to the server instance directory. - */ -exports.addShaderpacks = function(files, instanceDir) { - - const p = path.join(instanceDir, SHADER_DIR) - - exports.validateDir(p) - - for(let f of files) { - if(SHADER_REGEX.exec(f.name) != null) { - fs.moveSync(f.path, path.join(p, f.name)) - } - } - -} \ No newline at end of file diff --git a/app/assets/js/ipcconstants.js b/app/assets/js/ipcconstants.js deleted file mode 100644 index a1cd6385..00000000 --- a/app/assets/js/ipcconstants.js +++ /dev/null @@ -1,28 +0,0 @@ -// NOTE FOR THIRD-PARTY -// REPLACE THIS CLIENT ID WITH YOUR APPLICATION ID. -// SEE https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md -exports.AZURE_CLIENT_ID = '1ce6e35a-126f-48fd-97fb-54d143ac6d45' -// SEE NOTE ABOVE. - - -// Opcodes -exports.MSFT_OPCODE = { - OPEN_LOGIN: 'MSFT_AUTH_OPEN_LOGIN', - OPEN_LOGOUT: 'MSFT_AUTH_OPEN_LOGOUT', - REPLY_LOGIN: 'MSFT_AUTH_REPLY_LOGIN', - REPLY_LOGOUT: 'MSFT_AUTH_REPLY_LOGOUT' -} -// Reply types for REPLY opcode. -exports.MSFT_REPLY_TYPE = { - SUCCESS: 'MSFT_AUTH_REPLY_SUCCESS', - ERROR: 'MSFT_AUTH_REPLY_ERROR' -} -// Error types for ERROR reply. -exports.MSFT_ERROR = { - ALREADY_OPEN: 'MSFT_AUTH_ERR_ALREADY_OPEN', - NOT_FINISHED: 'MSFT_AUTH_ERR_NOT_FINISHED' -} - -exports.SHELL_OPCODE = { - TRASH_ITEM: 'TRASH_ITEM' -} \ No newline at end of file diff --git a/app/assets/js/isdev.js b/app/assets/js/isdev.js deleted file mode 100644 index 1ed55e5b..00000000 --- a/app/assets/js/isdev.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' -const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV, 10) === 1 -const isEnvSet = 'ELECTRON_IS_DEV' in process.env - -module.exports = isEnvSet ? getFromEnv : (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath)) \ No newline at end of file diff --git a/app/assets/js/langloader.js b/app/assets/js/langloader.js deleted file mode 100644 index 24ab84ae..00000000 --- a/app/assets/js/langloader.js +++ /dev/null @@ -1,21 +0,0 @@ -const fs = require('fs-extra') -const path = require('path') - -let lang - -exports.loadLanguage = function(id){ - lang = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.json`))) || {} -} - -exports.query = function(id){ - let query = id.split('.') - let res = lang - for(let q of query){ - res = res[q] - } - return res === lang ? {} : res -} - -exports.queryJS = function(id){ - return exports.query(`js.${id}`) -} \ No newline at end of file diff --git a/app/assets/js/preloader.js b/app/assets/js/preloader.js deleted file mode 100644 index f43eda8e..00000000 --- a/app/assets/js/preloader.js +++ /dev/null @@ -1,71 +0,0 @@ -const {ipcRenderer} = require('electron') -const fs = require('fs-extra') -const os = require('os') -const path = require('path') - -const ConfigManager = require('./configmanager') -const DistroManager = require('./distromanager') -const LangLoader = require('./langloader') -const { LoggerUtil } = require('helios-core') - -const logger = LoggerUtil.getLogger('Preloader') - -logger.info('Loading..') - -// Load ConfigManager -ConfigManager.load() - -// Load Strings -LangLoader.loadLanguage('en_US') - -function onDistroLoad(data){ - if(data != null){ - - // Resolve the selected server if its value has yet to be set. - if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null){ - logger.info('Determining default selected server..') - ConfigManager.setSelectedServer(data.getMainServer().getID()) - ConfigManager.save() - } - } - ipcRenderer.send('distributionIndexDone', data != null) -} - -// Ensure Distribution is downloaded and cached. -DistroManager.pullRemote().then((data) => { - logger.info('Loaded distribution index.') - - onDistroLoad(data) - -}).catch((err) => { - logger.info('Failed to load distribution index.') - logger.error(err) - - logger.info('Attempting to load an older version of the distribution index.') - // Try getting a local copy, better than nothing. - DistroManager.pullLocal().then((data) => { - logger.info('Successfully loaded an older version of the distribution index.') - - onDistroLoad(data) - - - }).catch((err) => { - - logger.info('Failed to load an older version of the distribution index.') - logger.info('Application cannot run.') - logger.error(err) - - onDistroLoad(null) - - }) - -}) - -// Clean up temp dir incase previous launches ended unexpectedly. -fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => { - if(err){ - logger.warn('Error while cleaning natives directory', err) - } else { - logger.info('Cleaned natives directory.') - } -}) \ No newline at end of file diff --git a/app/assets/js/processbuilder.js b/app/assets/js/processbuilder.js deleted file mode 100644 index 5efc1e62..00000000 --- a/app/assets/js/processbuilder.js +++ /dev/null @@ -1,891 +0,0 @@ -const AdmZip = require('adm-zip') -const child_process = require('child_process') -const crypto = require('crypto') -const fs = require('fs-extra') -const { LoggerUtil } = require('helios-core') -const os = require('os') -const path = require('path') -const { URL } = require('url') - -const { Util, Library } = require('./assetguard') -const ConfigManager = require('./configmanager') -const DistroManager = require('./distromanager') - -const logger = LoggerUtil.getLogger('ProcessBuilder') - -class ProcessBuilder { - - constructor(distroServer, versionData, forgeData, authUser, launcherVersion){ - this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.getID()) - this.commonDir = ConfigManager.getCommonDirectory() - this.server = distroServer - this.versionData = versionData - this.forgeData = forgeData - this.authUser = authUser - this.launcherVersion = launcherVersion - this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+ - this.fmlDir = path.join(this.gameDir, 'forgeModList.json') - this.llDir = path.join(this.gameDir, 'liteloaderModList.json') - this.libPath = path.join(this.commonDir, 'libraries') - - this.usingLiteLoader = false - this.llPath = null - } - - /** - * Convienence method to run the functions typically used to build a process. - */ - build(){ - fs.ensureDirSync(this.gameDir) - const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex')) - process.throwDeprecation = true - this.setupLiteLoader() - logger.info('Using liteloader:', this.usingLiteLoader) - const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.getID()).mods, this.server.getModules()) - - // Mod list below 1.13 - if(!Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ - this.constructJSONModList('forge', modObj.fMods, true) - if(this.usingLiteLoader){ - this.constructJSONModList('liteloader', modObj.lMods, true) - } - } - - const uberModArr = modObj.fMods.concat(modObj.lMods) - let args = this.constructJVMArguments(uberModArr, tempNativePath) - - if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ - //args = args.concat(this.constructModArguments(modObj.fMods)) - args = args.concat(this.constructModList(modObj.fMods)) - } - - logger.info('Launch Arguments:', args) - - const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.getID()), args, { - cwd: this.gameDir, - detached: ConfigManager.getLaunchDetached() - }) - - if(ConfigManager.getLaunchDetached()){ - child.unref() - } - - child.stdout.setEncoding('utf8') - child.stderr.setEncoding('utf8') - - child.stdout.on('data', (data) => { - data.trim().split('\n').forEach(x => console.log(`\x1b[32m[Minecraft]\x1b[0m ${x}`)) - - }) - child.stderr.on('data', (data) => { - data.trim().split('\n').forEach(x => console.log(`\x1b[31m[Minecraft]\x1b[0m ${x}`)) - }) - child.on('close', (code, signal) => { - logger.info('Exited with code', code) - fs.remove(tempNativePath, (err) => { - if(err){ - logger.warn('Error while deleting temp dir', err) - } else { - logger.info('Temp dir deleted successfully.') - } - }) - }) - - return child - } - - /** - * Get the platform specific classpath separator. On windows, this is a semicolon. - * On Unix, this is a colon. - * - * @returns {string} The classpath separator for the current operating system. - */ - static getClasspathSeparator() { - return process.platform === 'win32' ? ';' : ':' - } - - /** - * Determine if an optional mod is enabled from its configuration value. If the - * configuration value is null, the required object will be used to - * determine if it is enabled. - * - * A mod is enabled if: - * * The configuration is not null and one of the following: - * * The configuration is a boolean and true. - * * The configuration is an object and its 'value' property is true. - * * The configuration is null and one of the following: - * * The required object is null. - * * The required object's 'def' property is null or true. - * - * @param {Object | boolean} modCfg The mod configuration object. - * @param {Object} required Optional. The required object from the mod's distro declaration. - * @returns {boolean} True if the mod is enabled, false otherwise. - */ - static isModEnabled(modCfg, required = null){ - return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.isDefault() : true - } - - /** - * Function which performs a preliminary scan of the top level - * mods. If liteloader is present here, we setup the special liteloader - * launch options. Note that liteloader is only allowed as a top level - * mod. It must not be declared as a submodule. - */ - setupLiteLoader(){ - for(let ll of this.server.getModules()){ - if(ll.getType() === DistroManager.Types.LiteLoader){ - if(!ll.getRequired().isRequired()){ - const modCfg = ConfigManager.getModConfiguration(this.server.getID()).mods - if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.getRequired())){ - if(fs.existsSync(ll.getArtifact().getPath())){ - this.usingLiteLoader = true - this.llPath = ll.getArtifact().getPath() - } - } - } else { - if(fs.existsSync(ll.getArtifact().getPath())){ - this.usingLiteLoader = true - this.llPath = ll.getArtifact().getPath() - } - } - } - } - } - - /** - * Resolve an array of all enabled mods. These mods will be constructed into - * a mod list format and enabled at launch. - * - * @param {Object} modCfg The mod configuration object. - * @param {Array.} mdls An array of modules to parse. - * @returns {{fMods: Array., lMods: Array.}} An object which contains - * a list of enabled forge mods and litemods. - */ - resolveModConfiguration(modCfg, mdls){ - let fMods = [] - let lMods = [] - - for(let mdl of mdls){ - const type = mdl.getType() - if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){ - const o = !mdl.getRequired().isRequired() - const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessID()], mdl.getRequired()) - if(!o || (o && e)){ - if(mdl.hasSubModules()){ - const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessID()].mods, mdl.getSubModules()) - fMods = fMods.concat(v.fMods) - lMods = lMods.concat(v.lMods) - if(mdl.type === DistroManager.Types.LiteLoader){ - continue - } - } - if(mdl.type === DistroManager.Types.ForgeMod){ - fMods.push(mdl) - } else { - lMods.push(mdl) - } - } - } - } - - return { - fMods, - lMods - } - } - - _lteMinorVersion(version) { - return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= Number(version) - } - - /** - * Test to see if this version of forge requires the absolute: prefix - * on the modListFile repository field. - */ - _requiresAbsolute(){ - try { - if(this._lteMinorVersion(9)) { - return false - } - const ver = this.forgeData.id.split('-')[2] - const pts = ver.split('.') - const min = [14, 23, 3, 2655] - for(let i=0; i min[i]){ - return true - } - } - } catch (err) { - // We know old forge versions follow this format. - // Error must be caused by newer version. - } - - // Equal or errored - return true - } - - /** - * Construct a mod list json object. - * - * @param {'forge' | 'liteloader'} type The mod list type to construct. - * @param {Array.} mods An array of mods to add to the mod list. - * @param {boolean} save Optional. Whether or not we should save the mod list file. - */ - constructJSONModList(type, mods, save = false){ - const modList = { - repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + path.join(this.commonDir, 'modstore') - } - - const ids = [] - if(type === 'forge'){ - for(let mod of mods){ - ids.push(mod.getExtensionlessID()) - } - } else { - for(let mod of mods){ - ids.push(mod.getExtensionlessID() + '@' + mod.getExtension()) - } - } - modList.modRef = ids - - if(save){ - const json = JSON.stringify(modList, null, 4) - fs.writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, 'UTF-8') - } - - return modList - } - - // /** - // * Construct the mod argument list for forge 1.13 - // * - // * @param {Array.} mods An array of mods to add to the mod list. - // */ - // constructModArguments(mods){ - // const argStr = mods.map(mod => { - // return mod.getExtensionlessID() - // }).join(',') - - // if(argStr){ - // return [ - // '--fml.mavenRoots', - // path.join('..', '..', 'common', 'modstore'), - // '--fml.mods', - // argStr - // ] - // } else { - // return [] - // } - - // } - - /** - * Construct the mod argument list for forge 1.13 - * - * @param {Array.} mods An array of mods to add to the mod list. - */ - constructModList(mods) { - const writeBuffer = mods.map(mod => { - return mod.getExtensionlessID() - }).join('\n') - - if(writeBuffer) { - fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8') - return [ - '--fml.mavenRoots', - path.join('..', '..', 'common', 'modstore'), - '--fml.modLists', - this.forgeModListFile - ] - } else { - return [] - } - - } - - _processAutoConnectArg(args){ - if(ConfigManager.getAutoConnect() && this.server.isAutoConnect()){ - const serverURL = new URL('my://' + this.server.getAddress()) - args.push('--server') - args.push(serverURL.hostname) - if(serverURL.port){ - args.push('--port') - args.push(serverURL.port) - } - } - } - - /** - * Construct the argument array that will be passed to the JVM process. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - constructJVMArguments(mods, tempNativePath){ - if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ - return this._constructJVMArguments113(mods, tempNativePath) - } else { - return this._constructJVMArguments112(mods, tempNativePath) - } - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.12 and below. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - _constructJVMArguments112(mods, tempNativePath){ - - let args = [] - - // Classpath Argument - args.push('-cp') - args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator())) - - // Java Arguments - if(process.platform === 'darwin'){ - args.push('-Xdock:name=HeliosLauncher') - args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID())) - args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID())) - args = args.concat(ConfigManager.getJVMOptions(this.server.getID())) - args.push('-Djava.library.path=' + tempNativePath) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Forge Arguments - args = args.concat(this._resolveForgeArgs()) - - return args - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.13+ - * - * Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82 - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - _constructJVMArguments113(mods, tempNativePath){ - - const argDiscovery = /\${*(.*)}/ - - // JVM Arguments First - let args = this.versionData.arguments.jvm - - // Debug securejarhandler - // args.push('-Dbsl.debug=true') - - if(this.forgeData.arguments.jvm != null) { - for(const argStr of this.forgeData.arguments.jvm) { - args.push(argStr - .replaceAll('${library_directory}', this.libPath) - .replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator()) - .replaceAll('${version_name}', this.forgeData.id) - ) - } - } - - //args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml') - - // Java Arguments - if(process.platform === 'darwin'){ - args.push('-Xdock:name=HeliosLauncher') - args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.getID())) - args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID())) - args = args.concat(ConfigManager.getJVMOptions(this.server.getID())) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Vanilla Arguments - args = args.concat(this.versionData.arguments.game) - - for(let i=0; i { - return arg != null - }) - - return args - } - - /** - * Resolve the arguments required by forge. - * - * @returns {Array.} An array containing the arguments required by forge. - */ - _resolveForgeArgs(){ - const mcArgs = this.forgeData.minecraftArguments.split(' ') - const argDiscovery = /\${*(.*)}/ - - // Replace the declared variables with their proper values. - for(let i=0; i} list Array of classpath entries. - */ - _processClassPathList(list) { - - const ext = '.jar' - const extLen = ext.length - for(let i=0; i -1 && extIndex !== list[i].length - extLen) { - list[i] = list[i].substring(0, extIndex + extLen) - } - } - - } - - /** - * Resolve the full classpath argument list for this process. This method will resolve all Mojang-declared - * libraries as well as the libraries declared by the server. Since mods are permitted to declare libraries, - * this method requires all enabled mods as an input - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the paths of each library required by this process. - */ - classpathArg(mods, tempNativePath){ - let cpArgs = [] - - if(!Util.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) { - // Add the version.jar to the classpath. - // Must not be added to the classpath for Forge 1.17+. - const version = this.versionData.id - cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) - } - - - if(this.usingLiteLoader){ - cpArgs.push(this.llPath) - } - - // Resolve the Mojang declared libraries. - const mojangLibs = this._resolveMojangLibraries(tempNativePath) - - // Resolve the server declared libraries. - const servLibs = this._resolveServerLibraries(mods) - - // Merge libraries, server libs with the same - // maven identifier will override the mojang ones. - // Ex. 1.7.10 forge overrides mojang's guava with newer version. - const finalLibs = {...mojangLibs, ...servLibs} - cpArgs = cpArgs.concat(Object.values(finalLibs)) - - this._processClassPathList(cpArgs) - - return cpArgs - } - - /** - * Resolve the libraries defined by Mojang's version data. This method will also extract - * native libraries and point to the correct location for its classpath. - * - * TODO - clean up function - * - * @param {string} tempNativePath The path to store the native libraries. - * @returns {{[id: string]: string}} An object containing the paths of each library mojang declares. - */ - _resolveMojangLibraries(tempNativePath){ - const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/ - const libs = {} - - const libArr = this.versionData.libraries - fs.ensureDirSync(tempNativePath) - for(let i=0; i -1){ - shouldExclude = true - } - }) - - // Extract the file. - if(!shouldExclude){ - fs.writeFile(path.join(tempNativePath, fileName), zipEntries[i].getData(), (err) => { - if(err){ - logger.error('Error while extracting native library:', err) - } - }) - } - - } - } - // 1.19+ logic - else if(lib.name.includes('natives-')) { - - const regexTest = nativesRegex.exec(lib.name) - // const os = regexTest[1] - const arch = regexTest[2] ?? 'x64' - - if(arch != process.arch) { - continue - } - - // Extract the native library. - const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/', '.git', '.sha1'] - const artifact = lib.downloads.artifact - - // Location of native zip. - const to = path.join(this.libPath, artifact.path) - - let zip = new AdmZip(to) - let zipEntries = zip.getEntries() - - // Unzip the native zip. - for(let i=0; i -1){ - shouldExclude = true - } - }) - - const extractName = fileName.includes('/') ? fileName.substring(fileName.lastIndexOf('/')) : fileName - - // Extract the file. - if(!shouldExclude){ - fs.writeFile(path.join(tempNativePath, extractName), zipEntries[i].getData(), (err) => { - if(err){ - logger.error('Error while extracting native library:', err) - } - }) - } - - } - } - // No natives - else { - const dlInfo = lib.downloads - const artifact = dlInfo.artifact - const to = path.join(this.libPath, artifact.path) - const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':')) - libs[versionIndependentId] = to - } - } - } - - return libs - } - - /** - * Resolve the libraries declared by this server in order to add them to the classpath. - * This method will also check each enabled mod for libraries, as mods are permitted to - * declare libraries. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @returns {{[id: string]: string}} An object containing the paths of each library this server requires. - */ - _resolveServerLibraries(mods){ - const mdls = this.server.getModules() - let libs = {} - - // Locate Forge/Libraries - for(let mdl of mdls){ - const type = mdl.getType() - if(type === DistroManager.Types.ForgeHosted || type === DistroManager.Types.Library){ - libs[mdl.getVersionlessID()] = mdl.getArtifact().getPath() - if(mdl.hasSubModules()){ - const res = this._resolveModuleLibraries(mdl) - if(res.length > 0){ - libs = {...libs, ...res} - } - } - } - } - - //Check for any libraries in our mod list. - for(let i=0; i 0){ - libs = {...libs, ...res} - } - } - } - - return libs - } - - /** - * Recursively resolve the path of each library required by this module. - * - * @param {Object} mdl A module object from the server distro index. - * @returns {Array.} An array containing the paths of each library this module requires. - */ - _resolveModuleLibraries(mdl){ - if(!mdl.hasSubModules()){ - return [] - } - let libs = [] - for(let sm of mdl.getSubModules()){ - if(sm.getType() === DistroManager.Types.Library){ - - if(sm.getClasspath()) { - libs.push(sm.getArtifact().getPath()) - } - } - // If this module has submodules, we need to resolve the libraries for those. - // To avoid unnecessary recursive calls, base case is checked here. - if(mdl.hasSubModules()){ - const res = this._resolveModuleLibraries(sm) - if(res.length > 0){ - libs = libs.concat(res) - } - } - } - return libs - } - -} - -module.exports = ProcessBuilder \ No newline at end of file diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js deleted file mode 100644 index a7a2d0c6..00000000 --- a/app/assets/js/scripts/landing.js +++ /dev/null @@ -1,1150 +0,0 @@ -/** - * Script for landing.ejs - */ -// Requirements -const cp = require('child_process') -const crypto = require('crypto') -const { URL } = require('url') -const { MojangRestAPI, getServerStatus } = require('helios-core/mojang') - -// Internal Requirements -const DiscordWrapper = require('./assets/js/discordwrapper') -const ProcessBuilder = require('./assets/js/processbuilder') -const { Util } = require('./assets/js/assetguard') -const { RestResponseStatus, isDisplayableError } = require('helios-core/common') -const { stdout } = require('process') - -// Launch Elements -const launch_content = document.getElementById('launch_content') -const launch_details = document.getElementById('launch_details') -const launch_progress = document.getElementById('launch_progress') -const launch_progress_label = document.getElementById('launch_progress_label') -const launch_details_text = document.getElementById('launch_details_text') -const server_selection_button = document.getElementById('server_selection_button') -const user_text = document.getElementById('user_text') - -const loggerLanding = LoggerUtil.getLogger('Landing') - -/* Launch Progress Wrapper Functions */ - -/** - * Show/hide the loading area. - * - * @param {boolean} loading True if the loading area should be shown, otherwise false. - */ -function toggleLaunchArea(loading){ - if(loading){ - launch_details.style.display = 'flex' - launch_content.style.display = 'none' - } else { - launch_details.style.display = 'none' - launch_content.style.display = 'inline-flex' - } -} - -/** - * Set the details text of the loading area. - * - * @param {string} details The new text for the loading details. - */ -function setLaunchDetails(details){ - launch_details_text.innerHTML = details -} - -/** - * Set the value of the loading progress bar and display that value. - * - * @param {number} value The progress value. - * @param {number} max The total size. - * @param {number|string} percent Optional. The percentage to display on the progress label. - */ -function setLaunchPercentage(value, max, percent = ((value/max)*100)){ - launch_progress.setAttribute('max', max) - launch_progress.setAttribute('value', value) - launch_progress_label.innerHTML = percent + '%' -} - -/** - * Set the value of the OS progress bar and display that on the UI. - * - * @param {number} value The progress value. - * @param {number} max The total download size. - * @param {number|string} percent Optional. The percentage to display on the progress label. - */ -function setDownloadPercentage(value, max, percent = ((value/max)*100)){ - remote.getCurrentWindow().setProgressBar(value/max) - setLaunchPercentage(value, max, percent) -} - -/** - * Enable or disable the launch button. - * - * @param {boolean} val True to enable, false to disable. - */ -function setLaunchEnabled(val){ - document.getElementById('launch_button').disabled = !val -} - -// Bind launch button -document.getElementById('launch_button').addEventListener('click', function(e){ - loggerLanding.info('Launching game..') - const mcVersion = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() - const jExe = ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer()) - if(jExe == null){ - asyncSystemScan(mcVersion) - } else { - - setLaunchDetails(Lang.queryJS('landing.launch.pleaseWait')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const jg = new JavaGuard(mcVersion) - jg._validateJavaBinary(jExe).then((v) => { - loggerLanding.info('Java version meta', v) - if(v.valid){ - dlAsync() - } else { - asyncSystemScan(mcVersion) - } - }) - } -}) - -// Bind settings button -document.getElementById('settingsMediaButton').onclick = (e) => { - prepareSettings() - switchView(getCurrentView(), VIEWS.settings) -} - -// Bind avatar overlay button. -document.getElementById('avatarOverlay').onclick = (e) => { - prepareSettings() - switchView(getCurrentView(), VIEWS.settings, 500, 500, () => { - settingsNavItemListener(document.getElementById('settingsNavAccount'), false) - }) -} - -// Bind selected account -function updateSelectedAccount(authUser){ - let username = 'No Account Selected' - if(authUser != null){ - if(authUser.displayName != null){ - username = authUser.displayName - } - if(authUser.uuid != null){ - document.getElementById('avatarContainer').style.backgroundImage = `url('https://mc-heads.net/body/${authUser.uuid}/right')` - } - } - user_text.innerHTML = username -} -updateSelectedAccount(ConfigManager.getSelectedAccount()) - -// Bind selected server -function updateSelectedServer(serv){ - if(getCurrentView() === VIEWS.settings){ - fullSettingsSave() - } - ConfigManager.setSelectedServer(serv != null ? serv.getID() : null) - ConfigManager.save() - server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.getName() : 'No Server Selected') - if(getCurrentView() === VIEWS.settings){ - animateSettingsTabRefresh() - } - setLaunchEnabled(serv != null) -} -// Real text is set in uibinder.js on distributionIndexDone. -server_selection_button.innerHTML = '\u2022 Loading..' -server_selection_button.onclick = (e) => { - e.target.blur() - toggleServerSelection(true) -} - -// Update Mojang Status Color -const refreshMojangStatuses = async function(){ - loggerLanding.info('Refreshing Mojang Statuses..') - - let status = 'grey' - let tooltipEssentialHTML = '' - let tooltipNonEssentialHTML = '' - - const response = await MojangRestAPI.status() - let statuses - if(response.responseStatus === RestResponseStatus.SUCCESS) { - statuses = response.data - } else { - loggerLanding.warn('Unable to refresh Mojang service status.') - statuses = MojangRestAPI.getDefaultStatuses() - } - - greenCount = 0 - greyCount = 0 - - for(let i=0; i - - ${service.name} - ` - } else { - tooltipNonEssentialHTML += `
- - ${service.name} -
` - } - - if(service.status === 'yellow' && status !== 'red'){ - status = 'yellow' - } else if(service.status === 'red'){ - status = 'red' - } else { - if(service.status === 'grey'){ - ++greyCount - } - ++greenCount - } - - } - - if(greenCount === statuses.length){ - if(greyCount === statuses.length){ - status = 'grey' - } else { - status = 'green' - } - } - - document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML - document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML - document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status) -} - -const refreshServerStatus = async function(fade = false){ - loggerLanding.info('Refreshing Server Status') - const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) - - let pLabel = 'SERVER' - let pVal = 'OFFLINE' - - try { - const serverURL = new URL('my://' + serv.getAddress()) - - const servStat = await getServerStatus(47, serverURL.hostname, Number(serverURL.port)) - console.log(servStat) - pLabel = 'PLAYERS' - pVal = servStat.players.online + '/' + servStat.players.max - - } catch (err) { - loggerLanding.warn('Unable to refresh server status, assuming offline.') - loggerLanding.debug(err) - } - if(fade){ - $('#server_status_wrapper').fadeOut(250, () => { - document.getElementById('landingPlayerLabel').innerHTML = pLabel - document.getElementById('player_count').innerHTML = pVal - $('#server_status_wrapper').fadeIn(500) - }) - } else { - document.getElementById('landingPlayerLabel').innerHTML = pLabel - document.getElementById('player_count').innerHTML = pVal - } - -} - -refreshMojangStatuses() -// Server Status is refreshed in uibinder.js on distributionIndexDone. - -// Refresh statuses every hour. The status page itself refreshes every day so... -let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 60*60*1000) -// Set refresh rate to once every 5 minutes. -let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000) - -/** - * Shows an error overlay, toggles off the launch area. - * - * @param {string} title The overlay title. - * @param {string} desc The overlay description. - */ -function showLaunchFailure(title, desc){ - setOverlayContent( - title, - desc, - 'Okay' - ) - setOverlayHandler(null) - toggleOverlay(true) - toggleLaunchArea(false) -} - -/* System (Java) Scan */ - -let sysAEx -let scanAt - -let extractListener - -/** - * Asynchronously scan the system for valid Java installations. - * - * @param {string} mcVersion The Minecraft version we are scanning for. - * @param {boolean} launchAfter Whether we should begin to launch after scanning. - */ -function asyncSystemScan(mcVersion, launchAfter = true){ - - setLaunchDetails('Please wait..') - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const forkEnv = JSON.parse(JSON.stringify(process.env)) - forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory() - - // Fork a process to run validations. - sysAEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ - 'JavaGuard', - mcVersion - ], { - env: forkEnv, - stdio: 'pipe' - }) - // Stdout - sysAEx.stdio[1].setEncoding('utf8') - sysAEx.stdio[1].on('data', (data) => { - console.log(`\x1b[32m[SysAEx]\x1b[0m ${data}`) - }) - // Stderr - sysAEx.stdio[2].setEncoding('utf8') - sysAEx.stdio[2].on('data', (data) => { - console.log(`\x1b[31m[SysAEx]\x1b[0m ${data}`) - }) - - const javaVer = Util.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8' - - sysAEx.on('message', (m) => { - - if(m.context === 'validateJava'){ - if(m.result == null){ - // If the result is null, no valid Java installation was found. - // Show this information to the user. - setOverlayContent( - 'No Compatible
Java Installation Found', - `In order to join WesterosCraft, you need a 64-bit installation of Java ${javaVer}. Would you like us to install a copy?`, - 'Install Java', - 'Install Manually' - ) - setOverlayHandler(() => { - setLaunchDetails('Preparing Java Download..') - sysAEx.send({task: 'changeContext', class: 'AssetGuard', args: [ConfigManager.getCommonDirectory(),ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer())]}) - sysAEx.send({task: 'execute', function: '_enqueueOpenJDK', argsArr: [ConfigManager.getDataDirectory(), mcVersion]}) - toggleOverlay(false) - }) - setDismissHandler(() => { - $('#overlayContent').fadeOut(250, () => { - //$('#overlayDismiss').toggle(false) - setOverlayContent( - 'Java is Required
to Launch', - `A valid x64 installation of Java ${javaVer} is required to launch.

Please refer to our Java Management Guide for instructions on how to manually install Java.`, - 'I Understand', - 'Go Back' - ) - setOverlayHandler(() => { - toggleLaunchArea(false) - toggleOverlay(false) - }) - setDismissHandler(() => { - toggleOverlay(false, true) - asyncSystemScan() - }) - $('#overlayContent').fadeIn(250) - }) - }) - toggleOverlay(true, true) - - } else { - // Java installation found, use this to launch the game. - ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), m.result) - ConfigManager.save() - - // We need to make sure that the updated value is on the settings UI. - // Just incase the settings UI is already open. - settingsJavaExecVal.value = m.result - populateJavaExecDetails(settingsJavaExecVal.value) - - if(launchAfter){ - dlAsync() - } - sysAEx.disconnect() - } - } else if(m.context === '_enqueueOpenJDK'){ - - if(m.result === true){ - - // Oracle JRE enqueued successfully, begin download. - setLaunchDetails('Downloading Java..') - sysAEx.send({task: 'execute', function: 'processDlQueues', argsArr: [[{id:'java', limit:1}]]}) - - } else { - - // Oracle JRE enqueue failed. Probably due to a change in their website format. - // User will have to follow the guide to install Java. - setOverlayContent( - 'Unexpected Issue:
Java Download Failed', - 'Unfortunately we\'ve encountered an issue while attempting to install Java. You will need to manually install a copy. Please check out our Troubleshooting Guide for more details and instructions.', - 'I Understand' - ) - setOverlayHandler(() => { - toggleOverlay(false) - toggleLaunchArea(false) - }) - toggleOverlay(true) - sysAEx.disconnect() - - } - - } else if(m.context === 'progress'){ - - switch(m.data){ - case 'download': - // Downloading.. - setDownloadPercentage(m.value, m.total, m.percent) - break - } - - } else if(m.context === 'complete'){ - - switch(m.data){ - case 'download': { - // Show installing progress bar. - remote.getCurrentWindow().setProgressBar(2) - - // Wait for extration to complete. - const eLStr = 'Extracting' - let dotStr = '' - setLaunchDetails(eLStr) - extractListener = setInterval(() => { - if(dotStr.length >= 3){ - dotStr = '' - } else { - dotStr += '.' - } - setLaunchDetails(eLStr + dotStr) - }, 750) - break - } - case 'java': - // Download & extraction complete, remove the loading from the OS progress bar. - remote.getCurrentWindow().setProgressBar(-1) - - // Extraction completed successfully. - ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), m.args[0]) - ConfigManager.save() - - if(extractListener != null){ - clearInterval(extractListener) - extractListener = null - } - - setLaunchDetails('Java Installed!') - - if(launchAfter){ - dlAsync() - } - - sysAEx.disconnect() - break - } - - } else if(m.context === 'error'){ - console.log(m.error) - } - }) - - // Begin system Java scan. - setLaunchDetails('Checking system info..') - sysAEx.send({task: 'execute', function: 'validateJava', argsArr: [ConfigManager.getDataDirectory()]}) - -} - -// Keep reference to Minecraft Process -let proc -// Is DiscordRPC enabled -let hasRPC = false -// Joined server regex -// Change this if your server uses something different. -const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/ -const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+)$/ -const MIN_LINGER = 5000 - -let aEx -let serv -let versionData -let forgeData - -let progressListener - -function dlAsync(login = true){ - - // Login parameter is temporary for debug purposes. Allows testing the validation/downloads without - // launching the game. - - if(login) { - if(ConfigManager.getSelectedAccount() == null){ - loggerLanding.error('You must be logged into an account.') - return - } - } - - setLaunchDetails('Please wait..') - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') - - const forkEnv = JSON.parse(JSON.stringify(process.env)) - forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory() - - // Start AssetExec to run validations and downloads in a forked process. - aEx = cp.fork(path.join(__dirname, 'assets', 'js', 'assetexec.js'), [ - 'AssetGuard', - ConfigManager.getCommonDirectory(), - ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer()) - ], { - env: forkEnv, - stdio: 'pipe' - }) - // Stdout - aEx.stdio[1].setEncoding('utf8') - aEx.stdio[1].on('data', (data) => { - console.log(`\x1b[32m[AEx]\x1b[0m ${data}`) - }) - // Stderr - aEx.stdio[2].setEncoding('utf8') - aEx.stdio[2].on('data', (data) => { - console.log(`\x1b[31m[AEx]\x1b[0m ${data}`) - }) - aEx.on('error', (err) => { - loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure('Error During Launch', err.message || 'See console (CTRL + Shift + i) for more details.') - }) - aEx.on('close', (code, signal) => { - if(code !== 0){ - loggerLaunchSuite.error(`AssetExec exited with code ${code}, assuming error.`) - showLaunchFailure('Error During Launch', 'See console (CTRL + Shift + i) for more details.') - } - }) - - // Establish communications between the AssetExec and current process. - aEx.on('message', (m) => { - - if(m.context === 'validate'){ - switch(m.data){ - case 'distribution': - setLaunchPercentage(20, 100) - loggerLaunchSuite.info('Validated distibution index.') - setLaunchDetails('Loading version information..') - break - case 'version': - setLaunchPercentage(40, 100) - loggerLaunchSuite.info('Version data loaded.') - setLaunchDetails('Validating asset integrity..') - break - case 'assets': - setLaunchPercentage(60, 100) - loggerLaunchSuite.info('Asset Validation Complete') - setLaunchDetails('Validating library integrity..') - break - case 'libraries': - setLaunchPercentage(80, 100) - loggerLaunchSuite.info('Library validation complete.') - setLaunchDetails('Validating miscellaneous file integrity..') - break - case 'files': - setLaunchPercentage(100, 100) - loggerLaunchSuite.info('File validation complete.') - setLaunchDetails('Downloading files..') - break - } - } else if(m.context === 'progress'){ - switch(m.data){ - case 'assets': { - const perc = (m.value/m.total)*20 - setLaunchPercentage(40+perc, 100, parseInt(40+perc)) - break - } - case 'download': - setDownloadPercentage(m.value, m.total, m.percent) - break - case 'extract': { - // Show installing progress bar. - remote.getCurrentWindow().setProgressBar(2) - - // Download done, extracting. - const eLStr = 'Extracting libraries' - let dotStr = '' - setLaunchDetails(eLStr) - progressListener = setInterval(() => { - if(dotStr.length >= 3){ - dotStr = '' - } else { - dotStr += '.' - } - setLaunchDetails(eLStr + dotStr) - }, 750) - break - } - } - } else if(m.context === 'complete'){ - switch(m.data){ - case 'download': - // Download and extraction complete, remove the loading from the OS progress bar. - remote.getCurrentWindow().setProgressBar(-1) - if(progressListener != null){ - clearInterval(progressListener) - progressListener = null - } - - setLaunchDetails('Preparing to launch..') - break - } - } else if(m.context === 'error'){ - switch(m.data){ - case 'download': - loggerLaunchSuite.error('Error while downloading:', m.error) - - if(m.error.code === 'ENOENT'){ - showLaunchFailure( - 'Download Error', - 'Could not connect to the file server. Ensure that you are connected to the internet and try again.' - ) - } else { - showLaunchFailure( - 'Download Error', - 'Check the console (CTRL + Shift + i) for more details. Please try again.' - ) - } - - remote.getCurrentWindow().setProgressBar(-1) - - // Disconnect from AssetExec - aEx.disconnect() - break - } - } else if(m.context === 'validateEverything'){ - - let allGood = true - - // If these properties are not defined it's likely an error. - if(m.result.forgeData == null || m.result.versionData == null){ - loggerLaunchSuite.error('Error during validation:', m.result) - - loggerLaunchSuite.error('Error during launch', m.result.error) - showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.') - - allGood = false - } - - forgeData = m.result.forgeData - versionData = m.result.versionData - - if(login && allGood) { - const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) - let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion()) - setLaunchDetails('Launching game..') - - // const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/ - const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`) - - const onLoadComplete = () => { - toggleLaunchArea(false) - if(hasRPC){ - DiscordWrapper.updateDetails('Loading game..') - } - proc.stdout.on('data', gameStateChange) - proc.stdout.removeListener('data', tempListener) - proc.stderr.removeListener('data', gameErrorListener) - } - const start = Date.now() - - // Attach a temporary listener to the client output. - // Will wait for a certain bit of text meaning that - // the client application has started, and we can hide - // the progress bar stuff. - const tempListener = function(data){ - if(GAME_LAUNCH_REGEX.test(data.trim())){ - const diff = Date.now()-start - if(diff < MIN_LINGER) { - setTimeout(onLoadComplete, MIN_LINGER-diff) - } else { - onLoadComplete() - } - } - } - - // Listener for Discord RPC. - const gameStateChange = function(data){ - data = data.trim() - if(SERVER_JOINED_REGEX.test(data)){ - DiscordWrapper.updateDetails('Exploring the Realm!') - } else if(GAME_JOINED_REGEX.test(data)){ - DiscordWrapper.updateDetails('Sailing to Westeros!') - } - } - - const gameErrorListener = function(data){ - data = data.trim() - if(data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1){ - loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.') - showLaunchFailure('Error During Launch', 'The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.

To fix this issue, temporarily turn off your antivirus software and launch the game again.

If you have time, please submit an issue and let us know what antivirus software you use. We\'ll contact them and try to straighten things out.') - } - } - - try { - // Build Minecraft process. - proc = pb.build() - - // Bind listeners to stdout. - proc.stdout.on('data', tempListener) - proc.stderr.on('data', gameErrorListener) - - setLaunchDetails('Done. Enjoy the server!') - - // Init Discord Hook - const distro = DistroManager.getDistribution() - if(distro.discord != null && serv.discord != null){ - DiscordWrapper.initRPC(distro.discord, serv.discord) - hasRPC = true - proc.on('close', (code, signal) => { - loggerLaunchSuite.info('Shutting down Discord Rich Presence..') - DiscordWrapper.shutdownRPC() - hasRPC = false - proc = null - }) - } - - } catch(err) { - - loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.') - - } - } - - // Disconnect from AssetExec - aEx.disconnect() - - } - }) - - // Begin Validations - - // Validate Forge files. - setLaunchDetails('Loading server information..') - - refreshDistributionIndex(true, (data) => { - onDistroRefresh(data) - serv = data.getServer(ConfigManager.getSelectedServer()) - aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) - }, (err) => { - loggerLaunchSuite.info('Error while fetching a fresh copy of the distribution index.', err) - refreshDistributionIndex(false, (data) => { - onDistroRefresh(data) - serv = data.getServer(ConfigManager.getSelectedServer()) - aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) - }, (err) => { - loggerLaunchSuite.error('Unable to refresh distribution index.', err) - if(DistroManager.getDistribution() == null){ - showLaunchFailure('Fatal Error', 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.') - - // Disconnect from AssetExec - aEx.disconnect() - } else { - serv = data.getServer(ConfigManager.getSelectedServer()) - aEx.send({task: 'execute', function: 'validateEverything', argsArr: [ConfigManager.getSelectedServer(), DistroManager.isDevMode()]}) - } - }) - }) -} - -/** - * News Loading Functions - */ - -// DOM Cache -const newsContent = document.getElementById('newsContent') -const newsArticleTitle = document.getElementById('newsArticleTitle') -const newsArticleDate = document.getElementById('newsArticleDate') -const newsArticleAuthor = document.getElementById('newsArticleAuthor') -const newsArticleComments = document.getElementById('newsArticleComments') -const newsNavigationStatus = document.getElementById('newsNavigationStatus') -const newsArticleContentScrollable = document.getElementById('newsArticleContentScrollable') -const nELoadSpan = document.getElementById('nELoadSpan') - -// News slide caches. -let newsActive = false -let newsGlideCount = 0 - -/** - * Show the news UI via a slide animation. - * - * @param {boolean} up True to slide up, otherwise false. - */ -function slide_(up){ - const lCUpper = document.querySelector('#landingContainer > #upper') - const lCLLeft = document.querySelector('#landingContainer > #lower > #left') - const lCLCenter = document.querySelector('#landingContainer > #lower > #center') - const lCLRight = document.querySelector('#landingContainer > #lower > #right') - const newsBtn = document.querySelector('#landingContainer > #lower > #center #content') - const landingContainer = document.getElementById('landingContainer') - const newsContainer = document.querySelector('#landingContainer > #newsContainer') - - newsGlideCount++ - - if(up){ - lCUpper.style.top = '-200vh' - lCLLeft.style.top = '-200vh' - lCLCenter.style.top = '-200vh' - lCLRight.style.top = '-200vh' - newsBtn.style.top = '130vh' - newsContainer.style.top = '0px' - //date.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric'}) - //landingContainer.style.background = 'rgba(29, 29, 29, 0.55)' - landingContainer.style.background = 'rgba(0, 0, 0, 0.50)' - setTimeout(() => { - if(newsGlideCount === 1){ - lCLCenter.style.transition = 'none' - newsBtn.style.transition = 'none' - } - newsGlideCount-- - }, 2000) - } else { - setTimeout(() => { - newsGlideCount-- - }, 2000) - landingContainer.style.background = null - lCLCenter.style.transition = null - newsBtn.style.transition = null - newsContainer.style.top = '100%' - lCUpper.style.top = '0px' - lCLLeft.style.top = '0px' - lCLCenter.style.top = '0px' - lCLRight.style.top = '0px' - newsBtn.style.top = '10px' - } -} - -// Bind news button. -document.getElementById('newsButton').onclick = () => { - // Toggle tabbing. - if(newsActive){ - $('#landingContainer *').removeAttr('tabindex') - $('#newsContainer *').attr('tabindex', '-1') - } else { - $('#landingContainer *').attr('tabindex', '-1') - $('#newsContainer, #newsContainer *, #lower, #lower #center *').removeAttr('tabindex') - if(newsAlertShown){ - $('#newsButtonAlert').fadeOut(2000) - newsAlertShown = false - ConfigManager.setNewsCacheDismissed(true) - ConfigManager.save() - } - } - slide_(!newsActive) - newsActive = !newsActive -} - -// Array to store article meta. -let newsArr = null - -// News load animation listener. -let newsLoadingListener = null - -/** - * Set the news loading animation. - * - * @param {boolean} val True to set loading animation, otherwise false. - */ -function setNewsLoading(val){ - if(val){ - const nLStr = 'Checking for News' - let dotStr = '..' - nELoadSpan.innerHTML = nLStr + dotStr - newsLoadingListener = setInterval(() => { - if(dotStr.length >= 3){ - dotStr = '' - } else { - dotStr += '.' - } - nELoadSpan.innerHTML = nLStr + dotStr - }, 750) - } else { - if(newsLoadingListener != null){ - clearInterval(newsLoadingListener) - newsLoadingListener = null - } - } -} - -// Bind retry button. -newsErrorRetry.onclick = () => { - $('#newsErrorFailed').fadeOut(250, () => { - initNews() - $('#newsErrorLoading').fadeIn(250) - }) -} - -newsArticleContentScrollable.onscroll = (e) => { - if(e.target.scrollTop > Number.parseFloat($('.newsArticleSpacerTop').css('height'))){ - newsContent.setAttribute('scrolled', '') - } else { - newsContent.removeAttribute('scrolled') - } -} - -/** - * Reload the news without restarting. - * - * @returns {Promise.} A promise which resolves when the news - * content has finished loading and transitioning. - */ -function reloadNews(){ - return new Promise((resolve, reject) => { - $('#newsContent').fadeOut(250, () => { - $('#newsErrorLoading').fadeIn(250) - initNews().then(() => { - resolve() - }) - }) - }) -} - -let newsAlertShown = false - -/** - * Show the news alert indicating there is new news. - */ -function showNewsAlert(){ - newsAlertShown = true - $(newsButtonAlert).fadeIn(250) -} - -/** - * Initialize News UI. This will load the news and prepare - * the UI accordingly. - * - * @returns {Promise.} A promise which resolves when the news - * content has finished loading and transitioning. - */ -function initNews(){ - - return new Promise((resolve, reject) => { - setNewsLoading(true) - - let news = {} - loadNews().then(news => { - - newsArr = news.articles || null - - if(newsArr == null){ - // News Loading Failed - setNewsLoading(false) - - $('#newsErrorLoading').fadeOut(250, () => { - $('#newsErrorFailed').fadeIn(250, () => { - resolve() - }) - }) - } else if(newsArr.length === 0) { - // No News Articles - setNewsLoading(false) - - ConfigManager.setNewsCache({ - date: null, - content: null, - dismissed: false - }) - ConfigManager.save() - - $('#newsErrorLoading').fadeOut(250, () => { - $('#newsErrorNone').fadeIn(250, () => { - resolve() - }) - }) - } else { - // Success - setNewsLoading(false) - - const lN = newsArr[0] - const cached = ConfigManager.getNewsCache() - let newHash = crypto.createHash('sha1').update(lN.content).digest('hex') - let newDate = new Date(lN.date) - let isNew = false - - if(cached.date != null && cached.content != null){ - - if(new Date(cached.date) >= newDate){ - - // Compare Content - if(cached.content !== newHash){ - isNew = true - showNewsAlert() - } else { - if(!cached.dismissed){ - isNew = true - showNewsAlert() - } - } - - } else { - isNew = true - showNewsAlert() - } - - } else { - isNew = true - showNewsAlert() - } - - if(isNew){ - ConfigManager.setNewsCache({ - date: newDate.getTime(), - content: newHash, - dismissed: false - }) - ConfigManager.save() - } - - const switchHandler = (forward) => { - let cArt = parseInt(newsContent.getAttribute('article')) - let nxtArt = forward ? (cArt >= newsArr.length-1 ? 0 : cArt + 1) : (cArt <= 0 ? newsArr.length-1 : cArt - 1) - - displayArticle(newsArr[nxtArt], nxtArt+1) - } - - document.getElementById('newsNavigateRight').onclick = () => { switchHandler(true) } - document.getElementById('newsNavigateLeft').onclick = () => { switchHandler(false) } - - $('#newsErrorContainer').fadeOut(250, () => { - displayArticle(newsArr[0], 1) - $('#newsContent').fadeIn(250, () => { - resolve() - }) - }) - } - - }) - - }) -} - -/** - * Add keyboard controls to the news UI. Left and right arrows toggle - * between articles. If you are on the landing page, the up arrow will - * open the news UI. - */ -document.addEventListener('keydown', (e) => { - if(newsActive){ - if(e.key === 'ArrowRight' || e.key === 'ArrowLeft'){ - document.getElementById(e.key === 'ArrowRight' ? 'newsNavigateRight' : 'newsNavigateLeft').click() - } - // Interferes with scrolling an article using the down arrow. - // Not sure of a straight forward solution at this point. - // if(e.key === 'ArrowDown'){ - // document.getElementById('newsButton').click() - // } - } else { - if(getCurrentView() === VIEWS.landing){ - if(e.key === 'ArrowUp'){ - document.getElementById('newsButton').click() - } - } - } -}) - -/** - * Display a news article on the UI. - * - * @param {Object} articleObject The article meta object. - * @param {number} index The article index. - */ -function displayArticle(articleObject, index){ - newsArticleTitle.innerHTML = articleObject.title - newsArticleTitle.href = articleObject.link - newsArticleAuthor.innerHTML = 'by ' + articleObject.author - newsArticleDate.innerHTML = articleObject.date - newsArticleComments.innerHTML = articleObject.comments - newsArticleComments.href = articleObject.commentsLink - newsArticleContentScrollable.innerHTML = '
' + articleObject.content + '
' - Array.from(newsArticleContentScrollable.getElementsByClassName('bbCodeSpoilerButton')).forEach(v => { - v.onclick = () => { - const text = v.parentElement.getElementsByClassName('bbCodeSpoilerText')[0] - text.style.display = text.style.display === 'block' ? 'none' : 'block' - } - }) - newsNavigationStatus.innerHTML = index + ' of ' + newsArr.length - newsContent.setAttribute('article', index-1) -} - -/** - * Load news information from the RSS feed specified in the - * distribution index. - */ -function loadNews(){ - return new Promise((resolve, reject) => { - const distroData = DistroManager.getDistribution() - const newsFeed = distroData.getRSS() - const newsHost = new URL(newsFeed).origin + '/' - $.ajax({ - url: newsFeed, - success: (data) => { - const items = $(data).find('item') - const articles = [] - - for(let i=0; i { - resolve({ - articles: null - }) - }) - }) -} diff --git a/app/assets/js/scripts/login.js b/app/assets/js/scripts/login.js deleted file mode 100644 index 2b6d0c02..00000000 --- a/app/assets/js/scripts/login.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Script for login.ejs - */ -// Validation Regexes. -const validUsername = /^[a-zA-Z0-9_]{1,16}$/ -const basicEmail = /^\S+@\S+\.\S+$/ -//const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i - -// Login Elements -const loginCancelContainer = document.getElementById('loginCancelContainer') -const loginCancelButton = document.getElementById('loginCancelButton') -const loginEmailError = document.getElementById('loginEmailError') -const loginUsername = document.getElementById('loginUsername') -const loginPasswordError = document.getElementById('loginPasswordError') -const loginPassword = document.getElementById('loginPassword') -const checkmarkContainer = document.getElementById('checkmarkContainer') -const loginRememberOption = document.getElementById('loginRememberOption') -const loginButton = document.getElementById('loginButton') -const loginForm = document.getElementById('loginForm') - -// Control variables. -let lu = false, lp = false - - -/** - * Show a login error. - * - * @param {HTMLElement} element The element on which to display the error. - * @param {string} value The error text. - */ -function showError(element, value){ - element.innerHTML = value - element.style.opacity = 1 -} - -/** - * Shake a login error to add emphasis. - * - * @param {HTMLElement} element The element to shake. - */ -function shakeError(element){ - if(element.style.opacity == 1){ - element.classList.remove('shake') - void element.offsetWidth - element.classList.add('shake') - } -} - -/** - * Validate that an email field is neither empty nor invalid. - * - * @param {string} value The email value. - */ -function validateEmail(value){ - if(value){ - if(!basicEmail.test(value) && !validUsername.test(value)){ - showError(loginEmailError, Lang.queryJS('login.error.invalidValue')) - loginDisabled(true) - lu = false - } else { - loginEmailError.style.opacity = 0 - lu = true - if(lp){ - loginDisabled(false) - } - } - } else { - lu = false - showError(loginEmailError, Lang.queryJS('login.error.requiredValue')) - loginDisabled(true) - } -} - -/** - * Validate that the password field is not empty. - * - * @param {string} value The password value. - */ -function validatePassword(value){ - if(value){ - loginPasswordError.style.opacity = 0 - lp = true - if(lu){ - loginDisabled(false) - } - } else { - lp = false - showError(loginPasswordError, Lang.queryJS('login.error.invalidValue')) - loginDisabled(true) - } -} - -// Emphasize errors with shake when focus is lost. -loginUsername.addEventListener('focusout', (e) => { - validateEmail(e.target.value) - shakeError(loginEmailError) -}) -loginPassword.addEventListener('focusout', (e) => { - validatePassword(e.target.value) - shakeError(loginPasswordError) -}) - -// Validate input for each field. -loginUsername.addEventListener('input', (e) => { - validateEmail(e.target.value) -}) -loginPassword.addEventListener('input', (e) => { - validatePassword(e.target.value) -}) - -/** - * Enable or disable the login button. - * - * @param {boolean} v True to enable, false to disable. - */ -function loginDisabled(v){ - if(loginButton.disabled !== v){ - loginButton.disabled = v - } -} - -/** - * Enable or disable loading elements. - * - * @param {boolean} v True to enable, false to disable. - */ -function loginLoading(v){ - if(v){ - loginButton.setAttribute('loading', v) - loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.login'), Lang.queryJS('login.loggingIn')) - } else { - loginButton.removeAttribute('loading') - loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.login')) - } -} - -/** - * Enable or disable login form. - * - * @param {boolean} v True to enable, false to disable. - */ -function formDisabled(v){ - loginDisabled(v) - loginCancelButton.disabled = v - loginUsername.disabled = v - loginPassword.disabled = v - if(v){ - checkmarkContainer.setAttribute('disabled', v) - } else { - checkmarkContainer.removeAttribute('disabled') - } - loginRememberOption.disabled = v -} - -let loginViewOnSuccess = VIEWS.landing -let loginViewOnCancel = VIEWS.settings -let loginViewCancelHandler - -function loginCancelEnabled(val){ - if(val){ - $(loginCancelContainer).show() - } else { - $(loginCancelContainer).hide() - } -} - -loginCancelButton.onclick = (e) => { - switchView(getCurrentView(), loginViewOnCancel, 500, 500, () => { - loginUsername.value = '' - loginPassword.value = '' - loginCancelEnabled(false) - if(loginViewCancelHandler != null){ - loginViewCancelHandler() - loginViewCancelHandler = null - } - }) -} - -// Disable default form behavior. -loginForm.onsubmit = () => { return false } - -// Bind login button behavior. -loginButton.addEventListener('click', () => { - // Disable form. - formDisabled(true) - - // Show loading stuff. - loginLoading(true) - - AuthManager.addMojangAccount(loginUsername.value, loginPassword.value).then((value) => { - updateSelectedAccount(value) - loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success')) - $('.circle-loader').toggleClass('load-complete') - $('.checkmark').toggle() - setTimeout(() => { - switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => { - // Temporary workaround - if(loginViewOnSuccess === VIEWS.settings){ - prepareSettings() - } - loginViewOnSuccess = VIEWS.landing // Reset this for good measure. - loginCancelEnabled(false) // Reset this for good measure. - loginViewCancelHandler = null // Reset this for good measure. - loginUsername.value = '' - loginPassword.value = '' - $('.circle-loader').toggleClass('load-complete') - $('.checkmark').toggle() - loginLoading(false) - loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login')) - formDisabled(false) - }) - }, 1000) - }).catch((displayableError) => { - loginLoading(false) - - let actualDisplayableError - if(isDisplayableError(displayableError)) { - msftLoginLogger.error('Error while logging in.', displayableError) - actualDisplayableError = displayableError - } else { - // Uh oh. - msftLoginLogger.error('Unhandled error during login.', displayableError) - actualDisplayableError = { - title: 'Unknown Error During Login', - desc: 'An unknown error has occurred. Please see the console for details.' - } - } - - setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain')) - setOverlayHandler(() => { - formDisabled(false) - toggleOverlay(false) - }) - toggleOverlay(true) - }) - -}) \ No newline at end of file diff --git a/app/assets/js/scripts/loginOptions.js b/app/assets/js/scripts/loginOptions.js deleted file mode 100644 index cdb1bc8e..00000000 --- a/app/assets/js/scripts/loginOptions.js +++ /dev/null @@ -1,50 +0,0 @@ -const loginOptionsCancelContainer = document.getElementById('loginOptionCancelContainer') -const loginOptionMicrosoft = document.getElementById('loginOptionMicrosoft') -const loginOptionMojang = document.getElementById('loginOptionMojang') -const loginOptionsCancelButton = document.getElementById('loginOptionCancelButton') - -let loginOptionsCancellable = false - -let loginOptionsViewOnLoginSuccess -let loginOptionsViewOnLoginCancel -let loginOptionsViewOnCancel -let loginOptionsViewCancelHandler - -function loginOptionsCancelEnabled(val){ - if(val){ - $(loginOptionsCancelContainer).show() - } else { - $(loginOptionsCancelContainer).hide() - } -} - -loginOptionMicrosoft.onclick = (e) => { - switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => { - ipcRenderer.send( - MSFT_OPCODE.OPEN_LOGIN, - loginOptionsViewOnLoginSuccess, - loginOptionsViewOnLoginCancel - ) - }) -} - -loginOptionMojang.onclick = (e) => { - switchView(getCurrentView(), VIEWS.login, 500, 500, () => { - loginViewOnSuccess = loginOptionsViewOnLoginSuccess - loginViewOnCancel = loginOptionsViewOnLoginCancel - loginCancelEnabled(true) - }) -} - -loginOptionsCancelButton.onclick = (e) => { - switchView(getCurrentView(), loginOptionsViewOnCancel, 500, 500, () => { - // Clear login values (Mojang login) - // No cleanup needed for Microsoft. - loginUsername.value = '' - loginPassword.value = '' - if(loginOptionsViewCancelHandler != null){ - loginOptionsViewCancelHandler() - loginOptionsViewCancelHandler = null - } - }) -} \ No newline at end of file diff --git a/app/assets/js/scripts/overlay.js b/app/assets/js/scripts/overlay.js deleted file mode 100644 index cf2c5c98..00000000 --- a/app/assets/js/scripts/overlay.js +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Script for overlay.ejs - */ - -/* Overlay Wrapper Functions */ - -/** - * Check to see if the overlay is visible. - * - * @returns {boolean} Whether or not the overlay is visible. - */ -function isOverlayVisible(){ - return document.getElementById('main').hasAttribute('overlay') -} - -let overlayHandlerContent - -/** - * Overlay keydown handler for a non-dismissable overlay. - * - * @param {KeyboardEvent} e The keydown event. - */ -function overlayKeyHandler (e){ - if(e.key === 'Enter' || e.key === 'Escape'){ - document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEnter')[0].click() - } -} -/** - * Overlay keydown handler for a dismissable overlay. - * - * @param {KeyboardEvent} e The keydown event. - */ -function overlayKeyDismissableHandler (e){ - if(e.key === 'Enter'){ - document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEnter')[0].click() - } else if(e.key === 'Escape'){ - document.getElementById(overlayHandlerContent).getElementsByClassName('overlayKeybindEsc')[0].click() - } -} - -/** - * Bind overlay keydown listeners for escape and exit. - * - * @param {boolean} state Whether or not to add new event listeners. - * @param {string} content The overlay content which will be shown. - * @param {boolean} dismissable Whether or not the overlay is dismissable - */ -function bindOverlayKeys(state, content, dismissable){ - overlayHandlerContent = content - document.removeEventListener('keydown', overlayKeyHandler) - document.removeEventListener('keydown', overlayKeyDismissableHandler) - if(state){ - if(dismissable){ - document.addEventListener('keydown', overlayKeyDismissableHandler) - } else { - document.addEventListener('keydown', overlayKeyHandler) - } - } -} - -/** - * Toggle the visibility of the overlay. - * - * @param {boolean} toggleState True to display, false to hide. - * @param {boolean} dismissable Optional. True to show the dismiss option, otherwise false. - * @param {string} content Optional. The content div to be shown. - */ -function toggleOverlay(toggleState, dismissable = false, content = 'overlayContent'){ - if(toggleState == null){ - toggleState = !document.getElementById('main').hasAttribute('overlay') - } - if(typeof dismissable === 'string'){ - content = dismissable - dismissable = false - } - bindOverlayKeys(toggleState, content, dismissable) - if(toggleState){ - document.getElementById('main').setAttribute('overlay', true) - // Make things untabbable. - $('#main *').attr('tabindex', '-1') - $('#' + content).parent().children().hide() - $('#' + content).show() - if(dismissable){ - $('#overlayDismiss').show() - } else { - $('#overlayDismiss').hide() - } - $('#overlayContainer').fadeIn({ - duration: 250, - start: () => { - if(getCurrentView() === VIEWS.settings){ - document.getElementById('settingsContainer').style.backgroundColor = 'transparent' - } - } - }) - } else { - document.getElementById('main').removeAttribute('overlay') - // Make things tabbable. - $('#main *').removeAttr('tabindex') - $('#overlayContainer').fadeOut({ - duration: 250, - start: () => { - if(getCurrentView() === VIEWS.settings){ - document.getElementById('settingsContainer').style.backgroundColor = 'rgba(0, 0, 0, 0.50)' - } - }, - complete: () => { - $('#' + content).parent().children().hide() - $('#' + content).show() - if(dismissable){ - $('#overlayDismiss').show() - } else { - $('#overlayDismiss').hide() - } - } - }) - } -} - -function toggleServerSelection(toggleState){ - prepareServerSelectionList() - toggleOverlay(toggleState, true, 'serverSelectContent') -} - -/** - * Set the content of the overlay. - * - * @param {string} title Overlay title text. - * @param {string} description Overlay description text. - * @param {string} acknowledge Acknowledge button text. - * @param {string} dismiss Dismiss button text. - */ -function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss'){ - document.getElementById('overlayTitle').innerHTML = title - document.getElementById('overlayDesc').innerHTML = description - document.getElementById('overlayAcknowledge').innerHTML = acknowledge - document.getElementById('overlayDismiss').innerHTML = dismiss -} - -/** - * Set the onclick handler of the overlay acknowledge button. - * If the handler is null, a default handler will be added. - * - * @param {function} handler - */ -function setOverlayHandler(handler){ - if(handler == null){ - document.getElementById('overlayAcknowledge').onclick = () => { - toggleOverlay(false) - } - } else { - document.getElementById('overlayAcknowledge').onclick = handler - } -} - -/** - * Set the onclick handler of the overlay dismiss button. - * If the handler is null, a default handler will be added. - * - * @param {function} handler - */ -function setDismissHandler(handler){ - if(handler == null){ - document.getElementById('overlayDismiss').onclick = () => { - toggleOverlay(false) - } - } else { - document.getElementById('overlayDismiss').onclick = handler - } -} - -/* Server Select View */ - -document.getElementById('serverSelectConfirm').addEventListener('click', () => { - const listings = document.getElementsByClassName('serverListing') - for(let i=0; i 0){ - const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid')) - updateSelectedServer(serv) - toggleOverlay(false) - } -}) - -document.getElementById('accountSelectConfirm').addEventListener('click', () => { - const listings = document.getElementsByClassName('accountListing') - for(let i=0; i 0){ - const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid')) - ConfigManager.save() - updateSelectedAccount(authAcc) - if(getCurrentView() === VIEWS.settings) { - prepareSettings() - } - toggleOverlay(false) - validateSelectedAccount() - } -}) - -// Bind server select cancel button. -document.getElementById('serverSelectCancel').addEventListener('click', () => { - toggleOverlay(false) -}) - -document.getElementById('accountSelectCancel').addEventListener('click', () => { - $('#accountSelectContent').fadeOut(250, () => { - $('#overlayContent').fadeIn(250) - }) -}) - -function setServerListingHandlers(){ - const listings = Array.from(document.getElementsByClassName('serverListing')) - listings.map((val) => { - val.onclick = e => { - if(val.hasAttribute('selected')){ - return - } - const cListings = document.getElementsByClassName('serverListing') - for(let i=0; i { - val.onclick = e => { - if(val.hasAttribute('selected')){ - return - } - const cListings = document.getElementsByClassName('accountListing') - for(let i=0; i - -
- ${serv.getName()} - ${serv.getDescription()} -
-
${serv.getMinecraftVersion()}
-
${serv.getVersion()}
- ${serv.isMainServer() ? `
- - - - - - - - Main Server -
` : ''} -
-
- ` - } - document.getElementById('serverSelectListScrollable').innerHTML = htmlString - -} - -function populateAccountListings(){ - const accountsObj = ConfigManager.getAuthAccounts() - const accounts = Array.from(Object.keys(accountsObj), v=>accountsObj[v]) - let htmlString = '' - for(let i=0; i - -
${accounts[i].displayName}
- ` - } - document.getElementById('accountSelectListScrollable').innerHTML = htmlString - -} - -function prepareServerSelectionList(){ - populateServerListings() - setServerListingHandlers() -} - -function prepareAccountSelectionList(){ - populateAccountListings() - setAccountListingHandlers() -} \ No newline at end of file diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js deleted file mode 100644 index 2a4ad209..00000000 --- a/app/assets/js/scripts/settings.js +++ /dev/null @@ -1,1585 +0,0 @@ -// Requirements -const os = require('os') -const semver = require('semver') - -const { JavaGuard } = require('./assets/js/assetguard') -const DropinModUtil = require('./assets/js/dropinmodutil') -const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') - -const settingsState = { - invalid: new Set() -} - -function bindSettingsSelect(){ - for(let ele of document.getElementsByClassName('settingsSelectContainer')) { - const selectedDiv = ele.getElementsByClassName('settingsSelectSelected')[0] - - selectedDiv.onclick = (e) => { - e.stopPropagation() - closeSettingsSelect(e.target) - e.target.nextElementSibling.toggleAttribute('hidden') - e.target.classList.toggle('select-arrow-active') - } - } -} - -function closeSettingsSelect(el){ - for(let ele of document.getElementsByClassName('settingsSelectContainer')) { - const selectedDiv = ele.getElementsByClassName('settingsSelectSelected')[0] - const optionsDiv = ele.getElementsByClassName('settingsSelectOptions')[0] - - if(!(selectedDiv === el)) { - selectedDiv.classList.remove('select-arrow-active') - optionsDiv.setAttribute('hidden', '') - } - } -} - -/* If the user clicks anywhere outside the select box, -then close all select boxes: */ -document.addEventListener('click', closeSettingsSelect) - -bindSettingsSelect() - - -function bindFileSelectors(){ - for(let ele of document.getElementsByClassName('settingsFileSelButton')){ - - ele.onclick = async e => { - const isJavaExecSel = ele.id === 'settingsJavaExecSel' - const directoryDialog = ele.hasAttribute('dialogDirectory') && ele.getAttribute('dialogDirectory') == 'true' - const properties = directoryDialog ? ['openDirectory', 'createDirectory'] : ['openFile'] - - const options = { - properties - } - - if(ele.hasAttribute('dialogTitle')) { - options.title = ele.getAttribute('dialogTitle') - } - - if(isJavaExecSel && process.platform === 'win32') { - options.filters = [ - { name: 'Executables', extensions: ['exe'] }, - { name: 'All Files', extensions: ['*'] } - ] - } - - const res = await remote.dialog.showOpenDialog(remote.getCurrentWindow(), options) - if(!res.canceled) { - ele.previousElementSibling.value = res.filePaths[0] - if(isJavaExecSel) { - populateJavaExecDetails(ele.previousElementSibling.value) - } - } - } - } -} - -bindFileSelectors() - - -/** - * General Settings Functions - */ - -/** - * Bind value validators to the settings UI elements. These will - * validate against the criteria defined in the ConfigManager (if - * any). If the value is invalid, the UI will reflect this and saving - * will be disabled until the value is corrected. This is an automated - * process. More complex UI may need to be bound separately. - */ -function initSettingsValidators(){ - const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') - Array.from(sEls).map((v, index, arr) => { - const vFn = ConfigManager['validate' + v.getAttribute('cValue')] - if(typeof vFn === 'function'){ - if(v.tagName === 'INPUT'){ - if(v.type === 'number' || v.type === 'text'){ - v.addEventListener('keyup', (e) => { - const v = e.target - if(!vFn(v.value)){ - settingsState.invalid.add(v.id) - v.setAttribute('error', '') - settingsSaveDisabled(true) - } else { - if(v.hasAttribute('error')){ - v.removeAttribute('error') - settingsState.invalid.delete(v.id) - if(settingsState.invalid.size === 0){ - settingsSaveDisabled(false) - } - } - } - }) - } - } - } - - }) -} - -/** - * Load configuration values onto the UI. This is an automated process. - */ -function initSettingsValues(){ - const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') - Array.from(sEls).map((v, index, arr) => { - const cVal = v.getAttribute('cValue') - const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id. - const gFn = ConfigManager['get' + cVal] - const gFnOpts = [] - if(serverDependent) { - gFnOpts.push(ConfigManager.getSelectedServer()) - } - if(typeof gFn === 'function'){ - if(v.tagName === 'INPUT'){ - if(v.type === 'number' || v.type === 'text'){ - // Special Conditions - if(cVal === 'JavaExecutable'){ - v.value = gFn.apply(null, gFnOpts) - populateJavaExecDetails(v.value) - } else if (cVal === 'DataDirectory'){ - v.value = gFn.apply(null, gFnOpts) - } else if(cVal === 'JVMOptions'){ - v.value = gFn.apply(null, gFnOpts).join(' ') - } else { - v.value = gFn.apply(null, gFnOpts) - } - } else if(v.type === 'checkbox'){ - v.checked = gFn.apply(null, gFnOpts) - } - } else if(v.tagName === 'DIV'){ - if(v.classList.contains('rangeSlider')){ - // Special Conditions - if(cVal === 'MinRAM' || cVal === 'MaxRAM'){ - let val = gFn.apply(null, gFnOpts) - if(val.endsWith('M')){ - val = Number(val.substring(0, val.length-1))/1000 - } else { - val = Number.parseFloat(val) - } - - v.setAttribute('value', val) - } else { - v.setAttribute('value', Number.parseFloat(gFn.apply(null, gFnOpts))) - } - } - } - } - - }) -} - -/** - * Save the settings values. - */ -function saveSettingsValues(){ - const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') - Array.from(sEls).map((v, index, arr) => { - const cVal = v.getAttribute('cValue') - const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id. - const sFn = ConfigManager['set' + cVal] - const sFnOpts = [] - if(serverDependent) { - sFnOpts.push(ConfigManager.getSelectedServer()) - } - if(typeof sFn === 'function'){ - if(v.tagName === 'INPUT'){ - if(v.type === 'number' || v.type === 'text'){ - // Special Conditions - if(cVal === 'JVMOptions'){ - if(!v.value.trim()) { - sFnOpts.push([]) - sFn.apply(null, sFnOpts) - } else { - sFnOpts.push(v.value.trim().split(/\s+/)) - sFn.apply(null, sFnOpts) - } - } else { - sFnOpts.push(v.value) - sFn.apply(null, sFnOpts) - } - } else if(v.type === 'checkbox'){ - sFnOpts.push(v.checked) - sFn.apply(null, sFnOpts) - // Special Conditions - if(cVal === 'AllowPrerelease'){ - changeAllowPrerelease(v.checked) - } - } - } else if(v.tagName === 'DIV'){ - if(v.classList.contains('rangeSlider')){ - // Special Conditions - if(cVal === 'MinRAM' || cVal === 'MaxRAM'){ - let val = Number(v.getAttribute('value')) - if(val%1 > 0){ - val = val*1000 + 'M' - } else { - val = val + 'G' - } - - sFnOpts.push(val) - sFn.apply(null, sFnOpts) - } else { - sFnOpts.push(v.getAttribute('value')) - sFn.apply(null, sFnOpts) - } - } - } - } - }) -} - -let selectedSettingsTab = 'settingsTabAccount' - -/** - * Modify the settings container UI when the scroll threshold reaches - * a certain poin. - * - * @param {UIEvent} e The scroll event. - */ -function settingsTabScrollListener(e){ - if(e.target.scrollTop > Number.parseFloat(getComputedStyle(e.target.firstElementChild).marginTop)){ - document.getElementById('settingsContainer').setAttribute('scrolled', '') - } else { - document.getElementById('settingsContainer').removeAttribute('scrolled') - } -} - -/** - * Bind functionality for the settings navigation items. - */ -function setupSettingsTabs(){ - Array.from(document.getElementsByClassName('settingsNavItem')).map((val) => { - if(val.hasAttribute('rSc')){ - val.onclick = () => { - settingsNavItemListener(val) - } - } - }) -} - -/** - * Settings nav item onclick lisener. Function is exposed so that - * other UI elements can quickly toggle to a certain tab from other views. - * - * @param {Element} ele The nav item which has been clicked. - * @param {boolean} fade Optional. True to fade transition. - */ -function settingsNavItemListener(ele, fade = true){ - if(ele.hasAttribute('selected')){ - return - } - const navItems = document.getElementsByClassName('settingsNavItem') - for(let i=0; i { - $(`#${selectedSettingsTab}`).fadeIn({ - duration: 250, - start: () => { - settingsTabScrollListener({ - target: document.getElementById(selectedSettingsTab) - }) - } - }) - }) - } else { - $(`#${prevTab}`).hide(0, () => { - $(`#${selectedSettingsTab}`).show({ - duration: 0, - start: () => { - settingsTabScrollListener({ - target: document.getElementById(selectedSettingsTab) - }) - } - }) - }) - } -} - -const settingsNavDone = document.getElementById('settingsNavDone') - -/** - * Set if the settings save (done) button is disabled. - * - * @param {boolean} v True to disable, false to enable. - */ -function settingsSaveDisabled(v){ - settingsNavDone.disabled = v -} - -function fullSettingsSave() { - saveSettingsValues() - saveModConfiguration() - ConfigManager.save() - saveDropinModConfiguration() - saveShaderpackSettings() -} - -/* Closes the settings view and saves all data. */ -settingsNavDone.onclick = () => { - fullSettingsSave() - switchView(getCurrentView(), VIEWS.landing) -} - -/** - * Account Management Tab - */ - -const msftLoginLogger = LoggerUtil.getLogger('Microsoft Login') -const msftLogoutLogger = LoggerUtil.getLogger('Microsoft Logout') - -// Bind the add mojang account button. -document.getElementById('settingsAddMojangAccount').onclick = (e) => { - switchView(getCurrentView(), VIEWS.login, 500, 500, () => { - loginViewOnCancel = VIEWS.settings - loginViewOnSuccess = VIEWS.settings - loginCancelEnabled(true) - }) -} - -// Bind the add microsoft account button. -document.getElementById('settingsAddMicrosoftAccount').onclick = (e) => { - switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => { - ipcRenderer.send(MSFT_OPCODE.OPEN_LOGIN, VIEWS.settings, VIEWS.settings) - }) -} - -// Bind reply for Microsoft Login. -ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => { - if (arguments_[0] === MSFT_REPLY_TYPE.ERROR) { - - const viewOnClose = arguments_[2] - console.log(arguments_) - switchView(getCurrentView(), viewOnClose, 500, 500, () => { - - if(arguments_[1] === MSFT_ERROR.NOT_FINISHED) { - // User cancelled. - msftLoginLogger.info('Login cancelled by user.') - return - } - - // Unexpected error. - setOverlayContent( - 'Something Went Wrong', - 'Microsoft authentication failed. Please try again.', - 'OK' - ) - setOverlayHandler(() => { - toggleOverlay(false) - }) - toggleOverlay(true) - }) - } else if(arguments_[0] === MSFT_REPLY_TYPE.SUCCESS) { - const queryMap = arguments_[1] - const viewOnClose = arguments_[2] - - // Error from request to Microsoft. - if (Object.prototype.hasOwnProperty.call(queryMap, 'error')) { - switchView(getCurrentView(), viewOnClose, 500, 500, () => { - // TODO Dont know what these errors are. Just show them I guess. - // This is probably if you messed up the app registration with Azure. - let error = queryMap.error // Error might be 'access_denied' ? - let errorDesc = queryMap.error_description - console.log('Error getting authCode, is Azure application registered correctly?') - console.log(error) - console.log(errorDesc) - console.log('Full query map: ', queryMap) - setOverlayContent( - error, - errorDesc, - 'OK' - ) - setOverlayHandler(() => { - toggleOverlay(false) - }) - toggleOverlay(true) - - }) - } else { - - msftLoginLogger.info('Acquired authCode, proceeding with authentication.') - - const authCode = queryMap.code - AuthManager.addMicrosoftAccount(authCode).then(value => { - updateSelectedAccount(value) - switchView(getCurrentView(), viewOnClose, 500, 500, () => { - prepareSettings() - }) - }) - .catch((displayableError) => { - - let actualDisplayableError - if(isDisplayableError(displayableError)) { - msftLoginLogger.error('Error while logging in.', displayableError) - actualDisplayableError = displayableError - } else { - // Uh oh. - msftLoginLogger.error('Unhandled error during login.', displayableError) - actualDisplayableError = { - title: 'Unknown Error During Login', - desc: 'An unknown error has occurred. Please see the console for details.' - } - } - - switchView(getCurrentView(), viewOnClose, 500, 500, () => { - setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain')) - setOverlayHandler(() => { - toggleOverlay(false) - }) - toggleOverlay(true) - }) - }) - } - } -}) - -/** - * Bind functionality for the account selection buttons. If another account - * is selected, the UI of the previously selected account will be updated. - */ -function bindAuthAccountSelect(){ - Array.from(document.getElementsByClassName('settingsAuthAccountSelect')).map((val) => { - val.onclick = (e) => { - if(val.hasAttribute('selected')){ - return - } - const selectBtns = document.getElementsByClassName('settingsAuthAccountSelect') - for(let i=0; i { - val.onclick = (e) => { - let isLastAccount = false - if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){ - isLastAccount = true - setOverlayContent( - 'Warning
This is Your Last Account', - 'In order to use the launcher you must be logged into at least one account. You will need to login again after.

Are you sure you want to log out?', - 'I\'m Sure', - 'Cancel' - ) - setOverlayHandler(() => { - processLogOut(val, isLastAccount) - toggleOverlay(false) - }) - setDismissHandler(() => { - toggleOverlay(false) - }) - toggleOverlay(true, true) - } else { - processLogOut(val, isLastAccount) - } - - } - }) -} - -let msAccDomElementCache -/** - * Process a log out. - * - * @param {Element} val The log out button element. - * @param {boolean} isLastAccount If this logout is on the last added account. - */ -function processLogOut(val, isLastAccount){ - const parent = val.closest('.settingsAuthAccount') - const uuid = parent.getAttribute('uuid') - const prevSelAcc = ConfigManager.getSelectedAccount() - const targetAcc = ConfigManager.getAuthAccount(uuid) - if(targetAcc.type === 'microsoft') { - msAccDomElementCache = parent - switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => { - ipcRenderer.send(MSFT_OPCODE.OPEN_LOGOUT, uuid, isLastAccount) - }) - } else { - AuthManager.removeMojangAccount(uuid).then(() => { - if(!isLastAccount && uuid === prevSelAcc.uuid){ - const selAcc = ConfigManager.getSelectedAccount() - refreshAuthAccountSelected(selAcc.uuid) - updateSelectedAccount(selAcc) - validateSelectedAccount() - } - if(isLastAccount) { - loginOptionsCancelEnabled(false) - loginOptionsViewOnLoginSuccess = VIEWS.settings - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - switchView(getCurrentView(), VIEWS.loginOptions) - } - }) - $(parent).fadeOut(250, () => { - parent.remove() - }) - } -} - -// Bind reply for Microsoft Logout. -ipcRenderer.on(MSFT_OPCODE.REPLY_LOGOUT, (_, ...arguments_) => { - if (arguments_[0] === MSFT_REPLY_TYPE.ERROR) { - switchView(getCurrentView(), VIEWS.settings, 500, 500, () => { - - if(arguments_.length > 1 && arguments_[1] === MSFT_ERROR.NOT_FINISHED) { - // User cancelled. - msftLogoutLogger.info('Logout cancelled by user.') - return - } - - // Unexpected error. - setOverlayContent( - 'Something Went Wrong', - 'Microsoft logout failed. Please try again.', - 'OK' - ) - setOverlayHandler(() => { - toggleOverlay(false) - }) - toggleOverlay(true) - }) - } else if(arguments_[0] === MSFT_REPLY_TYPE.SUCCESS) { - - const uuid = arguments_[1] - const isLastAccount = arguments_[2] - const prevSelAcc = ConfigManager.getSelectedAccount() - - msftLogoutLogger.info('Logout Successful. uuid:', uuid) - - AuthManager.removeMicrosoftAccount(uuid) - .then(() => { - if(!isLastAccount && uuid === prevSelAcc.uuid){ - const selAcc = ConfigManager.getSelectedAccount() - refreshAuthAccountSelected(selAcc.uuid) - updateSelectedAccount(selAcc) - validateSelectedAccount() - } - if(isLastAccount) { - loginOptionsCancelEnabled(false) - loginOptionsViewOnLoginSuccess = VIEWS.settings - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - switchView(getCurrentView(), VIEWS.loginOptions) - } - if(msAccDomElementCache) { - msAccDomElementCache.remove() - msAccDomElementCache = null - } - }) - .finally(() => { - if(!isLastAccount) { - switchView(getCurrentView(), VIEWS.settings, 500, 500) - } - }) - - } -}) - -/** - * Refreshes the status of the selected account on the auth account - * elements. - * - * @param {string} uuid The UUID of the new selected account. - */ -function refreshAuthAccountSelected(uuid){ - Array.from(document.getElementsByClassName('settingsAuthAccount')).map((val) => { - const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0] - if(uuid === val.getAttribute('uuid')){ - selBtn.setAttribute('selected', '') - selBtn.innerHTML = 'Selected Account ✔' - } else { - if(selBtn.hasAttribute('selected')){ - selBtn.removeAttribute('selected') - } - selBtn.innerHTML = 'Select Account' - } - }) -} - -const settingsCurrentMicrosoftAccounts = document.getElementById('settingsCurrentMicrosoftAccounts') -const settingsCurrentMojangAccounts = document.getElementById('settingsCurrentMojangAccounts') - -/** - * Add auth account elements for each one stored in the authentication database. - */ -function populateAuthAccounts(){ - const authAccounts = ConfigManager.getAuthAccounts() - const authKeys = Object.keys(authAccounts) - if(authKeys.length === 0){ - return - } - const selectedUUID = ConfigManager.getSelectedAccount().uuid - - let microsoftAuthAccountStr = '' - let mojangAuthAccountStr = '' - - authKeys.forEach((val) => { - const acc = authAccounts[val] - - const accHtml = `
-
- ${acc.displayName} -
-
-
-
-
Username
-
${acc.displayName}
-
-
-
UUID
-
${acc.uuid}
-
-
-
- -
- -
-
-
-
` - - if(acc.type === 'microsoft') { - microsoftAuthAccountStr += accHtml - } else { - mojangAuthAccountStr += accHtml - } - - }) - - settingsCurrentMicrosoftAccounts.innerHTML = microsoftAuthAccountStr - settingsCurrentMojangAccounts.innerHTML = mojangAuthAccountStr -} - -/** - * Prepare the accounts tab for display. - */ -function prepareAccountsTab() { - populateAuthAccounts() - bindAuthAccountSelect() - bindAuthAccountLogOut() -} - -/** - * Minecraft Tab - */ - -/** - * Disable decimals, negative signs, and scientific notation. - */ -document.getElementById('settingsGameWidth').addEventListener('keydown', (e) => { - if(/^[-.eE]$/.test(e.key)){ - e.preventDefault() - } -}) -document.getElementById('settingsGameHeight').addEventListener('keydown', (e) => { - if(/^[-.eE]$/.test(e.key)){ - e.preventDefault() - } -}) - -/** - * Mods Tab - */ - -const settingsModsContainer = document.getElementById('settingsModsContainer') - -/** - * Resolve and update the mods on the UI. - */ -function resolveModsForUI(){ - const serv = ConfigManager.getSelectedServer() - - const distro = DistroManager.getDistribution() - const servConf = ConfigManager.getModConfiguration(serv) - - const modStr = parseModulesForUI(distro.getServer(serv).getModules(), false, servConf.mods) - - document.getElementById('settingsReqModsContent').innerHTML = modStr.reqMods - document.getElementById('settingsOptModsContent').innerHTML = modStr.optMods -} - -/** - * Recursively build the mod UI elements. - * - * @param {Object[]} mdls An array of modules to parse. - * @param {boolean} submodules Whether or not we are parsing submodules. - * @param {Object} servConf The server configuration object for this module level. - */ -function parseModulesForUI(mdls, submodules, servConf){ - - let reqMods = '' - let optMods = '' - - for(const mdl of mdls){ - - if(mdl.getType() === DistroManager.Types.ForgeMod || mdl.getType() === DistroManager.Types.LiteMod || mdl.getType() === DistroManager.Types.LiteLoader){ - - if(mdl.getRequired().isRequired()){ - - reqMods += `
-
-
-
-
- ${mdl.getName()} - v${mdl.getVersion()} -
-
- -
- ${mdl.hasSubModules() ? `
- ${Object.values(parseModulesForUI(mdl.getSubModules(), true, servConf[mdl.getVersionlessID()])).join('')} -
` : ''} -
` - - } else { - - const conf = servConf[mdl.getVersionlessID()] - const val = typeof conf === 'object' ? conf.value : conf - - optMods += `
-
-
-
-
- ${mdl.getName()} - v${mdl.getVersion()} -
-
- -
- ${mdl.hasSubModules() ? `
- ${Object.values(parseModulesForUI(mdl.getSubModules(), true, conf.mods)).join('')} -
` : ''} -
` - - } - } - } - - return { - reqMods, - optMods - } - -} - -/** - * Bind functionality to mod config toggle switches. Switching the value - * will also switch the status color on the left of the mod UI. - */ -function bindModsToggleSwitch(){ - const sEls = settingsModsContainer.querySelectorAll('[formod]') - Array.from(sEls).map((v, index, arr) => { - v.onchange = () => { - if(v.checked) { - document.getElementById(v.getAttribute('formod')).setAttribute('enabled', '') - } else { - document.getElementById(v.getAttribute('formod')).removeAttribute('enabled') - } - } - }) -} - - -/** - * Save the mod configuration based on the UI values. - */ -function saveModConfiguration(){ - const serv = ConfigManager.getSelectedServer() - const modConf = ConfigManager.getModConfiguration(serv) - modConf.mods = _saveModConfiguration(modConf.mods) - ConfigManager.setModConfiguration(serv, modConf) -} - -/** - * Recursively save mod config with submods. - * - * @param {Object} modConf Mod config object to save. - */ -function _saveModConfiguration(modConf){ - for(let m of Object.entries(modConf)){ - const tSwitch = settingsModsContainer.querySelectorAll(`[formod='${m[0]}']`) - if(!tSwitch[0].hasAttribute('dropin')){ - if(typeof m[1] === 'boolean'){ - modConf[m[0]] = tSwitch[0].checked - } else { - if(m[1] != null){ - if(tSwitch.length > 0){ - modConf[m[0]].value = tSwitch[0].checked - } - modConf[m[0]].mods = _saveModConfiguration(modConf[m[0]].mods) - } - } - } - } - return modConf -} - -// Drop-in mod elements. - -let CACHE_SETTINGS_MODS_DIR -let CACHE_DROPIN_MODS - -/** - * Resolve any located drop-in mods for this server and - * populate the results onto the UI. - */ -function resolveDropinModsForUI(){ - const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) - CACHE_SETTINGS_MODS_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID(), 'mods') - CACHE_DROPIN_MODS = DropinModUtil.scanForDropinMods(CACHE_SETTINGS_MODS_DIR, serv.getMinecraftVersion()) - - let dropinMods = '' - - for(dropin of CACHE_DROPIN_MODS){ - dropinMods += `
-
-
-
-
- ${dropin.name} -
- -
-
-
- -
-
` - } - - document.getElementById('settingsDropinModsContent').innerHTML = dropinMods -} - -/** - * Bind the remove button for each loaded drop-in mod. - */ -function bindDropinModsRemoveButton(){ - const sEls = settingsModsContainer.querySelectorAll('[remmod]') - Array.from(sEls).map((v, index, arr) => { - v.onclick = async () => { - const fullName = v.getAttribute('remmod') - const res = await DropinModUtil.deleteDropinMod(CACHE_SETTINGS_MODS_DIR, fullName) - if(res){ - document.getElementById(fullName).remove() - } else { - setOverlayContent( - `Failed to Delete
Drop-in Mod ${fullName}`, - 'Make sure the file is not in use and try again.', - 'Okay' - ) - setOverlayHandler(null) - toggleOverlay(true) - } - } - }) -} - -/** - * Bind functionality to the file system button for the selected - * server configuration. - */ -function bindDropinModFileSystemButton(){ - const fsBtn = document.getElementById('settingsDropinFileSystemButton') - fsBtn.onclick = () => { - DropinModUtil.validateDir(CACHE_SETTINGS_MODS_DIR) - shell.openPath(CACHE_SETTINGS_MODS_DIR) - } - fsBtn.ondragenter = e => { - e.dataTransfer.dropEffect = 'move' - fsBtn.setAttribute('drag', '') - e.preventDefault() - } - fsBtn.ondragover = e => { - e.preventDefault() - } - fsBtn.ondragleave = e => { - fsBtn.removeAttribute('drag') - } - - fsBtn.ondrop = e => { - fsBtn.removeAttribute('drag') - e.preventDefault() - - DropinModUtil.addDropinMods(e.dataTransfer.files, CACHE_SETTINGS_MODS_DIR) - reloadDropinMods() - } -} - -/** - * Save drop-in mod states. Enabling and disabling is just a matter - * of adding/removing the .disabled extension. - */ -function saveDropinModConfiguration(){ - for(dropin of CACHE_DROPIN_MODS){ - const dropinUI = document.getElementById(dropin.fullName) - if(dropinUI != null){ - const dropinUIEnabled = dropinUI.hasAttribute('enabled') - if(DropinModUtil.isDropinModEnabled(dropin.fullName) != dropinUIEnabled){ - DropinModUtil.toggleDropinMod(CACHE_SETTINGS_MODS_DIR, dropin.fullName, dropinUIEnabled).catch(err => { - if(!isOverlayVisible()){ - setOverlayContent( - 'Failed to Toggle
One or More Drop-in Mods', - err.message, - 'Okay' - ) - setOverlayHandler(null) - toggleOverlay(true) - } - }) - } - } - } -} - -// Refresh the drop-in mods when F5 is pressed. -// Only active on the mods tab. -document.addEventListener('keydown', (e) => { - if(getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods'){ - if(e.key === 'F5'){ - reloadDropinMods() - saveShaderpackSettings() - resolveShaderpacksForUI() - } - } -}) - -function reloadDropinMods(){ - resolveDropinModsForUI() - bindDropinModsRemoveButton() - bindDropinModFileSystemButton() - bindModsToggleSwitch() -} - -// Shaderpack - -let CACHE_SETTINGS_INSTANCE_DIR -let CACHE_SHADERPACKS -let CACHE_SELECTED_SHADERPACK - -/** - * Load shaderpack information. - */ -function resolveShaderpacksForUI(){ - const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) - CACHE_SETTINGS_INSTANCE_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID()) - CACHE_SHADERPACKS = DropinModUtil.scanForShaderpacks(CACHE_SETTINGS_INSTANCE_DIR) - CACHE_SELECTED_SHADERPACK = DropinModUtil.getEnabledShaderpack(CACHE_SETTINGS_INSTANCE_DIR) - - setShadersOptions(CACHE_SHADERPACKS, CACHE_SELECTED_SHADERPACK) -} - -function setShadersOptions(arr, selected){ - const cont = document.getElementById('settingsShadersOptions') - cont.innerHTML = '' - for(let opt of arr) { - const d = document.createElement('DIV') - d.innerHTML = opt.name - d.setAttribute('value', opt.fullName) - if(opt.fullName === selected) { - d.setAttribute('selected', '') - document.getElementById('settingsShadersSelected').innerHTML = opt.name - } - d.addEventListener('click', function(e) { - this.parentNode.previousElementSibling.innerHTML = this.innerHTML - for(let sib of this.parentNode.children){ - sib.removeAttribute('selected') - } - this.setAttribute('selected', '') - closeSettingsSelect() - }) - cont.appendChild(d) - } -} - -function saveShaderpackSettings(){ - let sel = 'OFF' - for(let opt of document.getElementById('settingsShadersOptions').childNodes){ - if(opt.hasAttribute('selected')){ - sel = opt.getAttribute('value') - } - } - DropinModUtil.setEnabledShaderpack(CACHE_SETTINGS_INSTANCE_DIR, sel) -} - -function bindShaderpackButton() { - const spBtn = document.getElementById('settingsShaderpackButton') - spBtn.onclick = () => { - const p = path.join(CACHE_SETTINGS_INSTANCE_DIR, 'shaderpacks') - DropinModUtil.validateDir(p) - shell.openPath(p) - } - spBtn.ondragenter = e => { - e.dataTransfer.dropEffect = 'move' - spBtn.setAttribute('drag', '') - e.preventDefault() - } - spBtn.ondragover = e => { - e.preventDefault() - } - spBtn.ondragleave = e => { - spBtn.removeAttribute('drag') - } - - spBtn.ondrop = e => { - spBtn.removeAttribute('drag') - e.preventDefault() - - DropinModUtil.addShaderpacks(e.dataTransfer.files, CACHE_SETTINGS_INSTANCE_DIR) - saveShaderpackSettings() - resolveShaderpacksForUI() - } -} - -// Server status bar functions. - -/** - * Load the currently selected server information onto the mods tab. - */ -function loadSelectedServerOnModsTab(){ - const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) - - for(const el of document.getElementsByClassName('settingsSelServContent')) { - el.innerHTML = ` - -
- ${serv.getName()} - ${serv.getDescription()} -
-
${serv.getMinecraftVersion()}
-
${serv.getVersion()}
- ${serv.isMainServer() ? `
- - - - - - - - Main Server -
` : ''} -
-
- ` - } -} - -// Bind functionality to the server switch button. -Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => { - el.addEventListener('click', (e) => { - e.target.blur() - toggleServerSelection(true) - }) -}) - -/** - * Save mod configuration for the current selected server. - */ -function saveAllModConfigurations(){ - saveModConfiguration() - ConfigManager.save() - saveDropinModConfiguration() -} - -/** - * Function to refresh the current tab whenever the selected - * server is changed. - */ -function animateSettingsTabRefresh(){ - $(`#${selectedSettingsTab}`).fadeOut(500, () => { - prepareSettings() - $(`#${selectedSettingsTab}`).fadeIn(500) - }) -} - -/** - * Prepare the Mods tab for display. - */ -function prepareModsTab(first){ - resolveModsForUI() - resolveDropinModsForUI() - resolveShaderpacksForUI() - bindDropinModsRemoveButton() - bindDropinModFileSystemButton() - bindShaderpackButton() - bindModsToggleSwitch() - loadSelectedServerOnModsTab() -} - -/** - * Java Tab - */ - -// DOM Cache -const settingsMaxRAMRange = document.getElementById('settingsMaxRAMRange') -const settingsMinRAMRange = document.getElementById('settingsMinRAMRange') -const settingsMaxRAMLabel = document.getElementById('settingsMaxRAMLabel') -const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel') -const settingsMemoryTotal = document.getElementById('settingsMemoryTotal') -const settingsMemoryAvail = document.getElementById('settingsMemoryAvail') -const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails') -const settingsJavaReqDesc = document.getElementById('settingsJavaReqDesc') -const settingsJvmOptsLink = document.getElementById('settingsJvmOptsLink') - -// Store maximum memory values. -const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM() -const SETTINGS_MIN_MEMORY = ConfigManager.getAbsoluteMinRAM() - -// Set the max and min values for the ranged sliders. -settingsMaxRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY) -settingsMaxRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY) -settingsMinRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY) -settingsMinRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY ) - -// Bind on change event for min memory container. -settingsMinRAMRange.onchange = (e) => { - - // Current range values - const sMaxV = Number(settingsMaxRAMRange.getAttribute('value')) - const sMinV = Number(settingsMinRAMRange.getAttribute('value')) - - // Get reference to range bar. - const bar = e.target.getElementsByClassName('rangeSliderBar')[0] - // Calculate effective total memory. - const max = (os.totalmem()-1000000000)/1000000000 - - // Change range bar color based on the selected value. - if(sMinV >= max/2){ - bar.style.background = '#e86060' - } else if(sMinV >= max/4) { - bar.style.background = '#e8e18b' - } else { - bar.style.background = null - } - - // Increase maximum memory if the minimum exceeds its value. - if(sMaxV < sMinV){ - const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange) - updateRangedSlider(settingsMaxRAMRange, sMinV, - ((sMinV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc) - settingsMaxRAMLabel.innerHTML = sMinV.toFixed(1) + 'G' - } - - // Update label - settingsMinRAMLabel.innerHTML = sMinV.toFixed(1) + 'G' -} - -// Bind on change event for max memory container. -settingsMaxRAMRange.onchange = (e) => { - // Current range values - const sMaxV = Number(settingsMaxRAMRange.getAttribute('value')) - const sMinV = Number(settingsMinRAMRange.getAttribute('value')) - - // Get reference to range bar. - const bar = e.target.getElementsByClassName('rangeSliderBar')[0] - // Calculate effective total memory. - const max = (os.totalmem()-1000000000)/1000000000 - - // Change range bar color based on the selected value. - if(sMaxV >= max/2){ - bar.style.background = '#e86060' - } else if(sMaxV >= max/4) { - bar.style.background = '#e8e18b' - } else { - bar.style.background = null - } - - // Decrease the minimum memory if the maximum value is less. - if(sMaxV < sMinV){ - const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange) - updateRangedSlider(settingsMinRAMRange, sMaxV, - ((sMaxV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc) - settingsMinRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G' - } - settingsMaxRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G' -} - -/** - * Calculate common values for a ranged slider. - * - * @param {Element} v The range slider to calculate against. - * @returns {Object} An object with meta values for the provided ranged slider. - */ -function calculateRangeSliderMeta(v){ - const val = { - max: Number(v.getAttribute('max')), - min: Number(v.getAttribute('min')), - step: Number(v.getAttribute('step')), - } - val.ticks = (val.max-val.min)/val.step - val.inc = 100/val.ticks - return val -} - -/** - * Binds functionality to the ranged sliders. They're more than - * just divs now :'). - */ -function bindRangeSlider(){ - Array.from(document.getElementsByClassName('rangeSlider')).map((v) => { - - // Reference the track (thumb). - const track = v.getElementsByClassName('rangeSliderTrack')[0] - - // Set the initial slider value. - const value = v.getAttribute('value') - const sliderMeta = calculateRangeSliderMeta(v) - - updateRangedSlider(v, value, ((value-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc) - - // The magic happens when we click on the track. - track.onmousedown = (e) => { - - // Stop moving the track on mouse up. - document.onmouseup = (e) => { - document.onmousemove = null - document.onmouseup = null - } - - // Move slider according to the mouse position. - document.onmousemove = (e) => { - - // Distance from the beginning of the bar in pixels. - const diff = e.pageX - v.offsetLeft - track.offsetWidth/2 - - // Don't move the track off the bar. - if(diff >= 0 && diff <= v.offsetWidth-track.offsetWidth/2){ - - // Convert the difference to a percentage. - const perc = (diff/v.offsetWidth)*100 - // Calculate the percentage of the closest notch. - const notch = Number(perc/sliderMeta.inc).toFixed(0)*sliderMeta.inc - - // If we're close to that notch, stick to it. - if(Math.abs(perc-notch) < sliderMeta.inc/2){ - updateRangedSlider(v, sliderMeta.min+(sliderMeta.step*(notch/sliderMeta.inc)), notch) - } - } - } - } - }) -} - -/** - * Update a ranged slider's value and position. - * - * @param {Element} element The ranged slider to update. - * @param {string | number} value The new value for the ranged slider. - * @param {number} notch The notch that the slider should now be at. - */ -function updateRangedSlider(element, value, notch){ - const oldVal = element.getAttribute('value') - const bar = element.getElementsByClassName('rangeSliderBar')[0] - const track = element.getElementsByClassName('rangeSliderTrack')[0] - - element.setAttribute('value', value) - - if(notch < 0){ - notch = 0 - } else if(notch > 100) { - notch = 100 - } - - const event = new MouseEvent('change', { - target: element, - type: 'change', - bubbles: false, - cancelable: true - }) - - let cancelled = !element.dispatchEvent(event) - - if(!cancelled){ - track.style.left = notch + '%' - bar.style.width = notch + '%' - } else { - element.setAttribute('value', oldVal) - } -} - -/** - * Display the total and available RAM. - */ -function populateMemoryStatus(){ - settingsMemoryTotal.innerHTML = Number((os.totalmem()-1000000000)/1000000000).toFixed(1) + 'G' - settingsMemoryAvail.innerHTML = Number(os.freemem()/1000000000).toFixed(1) + 'G' -} - -/** - * Validate the provided executable path and display the data on - * the UI. - * - * @param {string} execPath The executable path to populate against. - */ -function populateJavaExecDetails(execPath){ - const jg = new JavaGuard(DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()) - jg._validateJavaBinary(execPath).then(v => { - if(v.valid){ - const vendor = v.vendor != null ? ` (${v.vendor})` : '' - if(v.version.major < 9) { - settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major} Update ${v.version.update} (x${v.arch})${vendor}` - } else { - settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major}.${v.version.minor}.${v.version.revision} (x${v.arch})${vendor}` - } - } else { - settingsJavaExecDetails.innerHTML = 'Invalid Selection' - } - }) -} - -function populateJavaReqDesc() { - const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() - if(Util.mcVersionAtLeast('1.17', mcVer)) { - settingsJavaReqDesc.innerHTML = 'Requires Java 17 x64.' - } else { - settingsJavaReqDesc.innerHTML = 'Requires Java 8 x64.' - } -} - -function populateJvmOptsLink() { - const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() - if(Util.mcVersionAtLeast('1.17', mcVer)) { - settingsJvmOptsLink.innerHTML = 'Available Options for Java 17 (HotSpot VM)' - settingsJvmOptsLink.href = 'https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#extra-options-for-java' - } else { - settingsJvmOptsLink.innerHTML = 'Available Options for Java 8 (HotSpot VM)' - settingsJvmOptsLink.href = `https://docs.oracle.com/javase/8/docs/technotes/tools/${process.platform === 'win32' ? 'windows' : 'unix'}/java.html` - } -} - -/** - * Prepare the Java tab for display. - */ -function prepareJavaTab(){ - bindRangeSlider() - populateMemoryStatus() - populateJavaReqDesc() - populateJvmOptsLink() -} - -/** - * About Tab - */ - -const settingsTabAbout = document.getElementById('settingsTabAbout') -const settingsAboutChangelogTitle = settingsTabAbout.getElementsByClassName('settingsChangelogTitle')[0] -const settingsAboutChangelogText = settingsTabAbout.getElementsByClassName('settingsChangelogText')[0] -const settingsAboutChangelogButton = settingsTabAbout.getElementsByClassName('settingsChangelogButton')[0] - -// Bind the devtools toggle button. -document.getElementById('settingsAboutDevToolsButton').onclick = (e) => { - let window = remote.getCurrentWindow() - window.toggleDevTools() -} - -/** - * Return whether or not the provided version is a prerelease. - * - * @param {string} version The semver version to test. - * @returns {boolean} True if the version is a prerelease, otherwise false. - */ -function isPrerelease(version){ - const preRelComp = semver.prerelease(version) - return preRelComp != null && preRelComp.length > 0 -} - -/** - * Utility method to display version information on the - * About and Update settings tabs. - * - * @param {string} version The semver version to display. - * @param {Element} valueElement The value element. - * @param {Element} titleElement The title element. - * @param {Element} checkElement The check mark element. - */ -function populateVersionInformation(version, valueElement, titleElement, checkElement){ - valueElement.innerHTML = version - if(isPrerelease(version)){ - titleElement.innerHTML = 'Pre-release' - titleElement.style.color = '#ff886d' - checkElement.style.background = '#ff886d' - } else { - titleElement.innerHTML = 'Stable Release' - titleElement.style.color = null - checkElement.style.background = null - } -} - -/** - * Retrieve the version information and display it on the UI. - */ -function populateAboutVersionInformation(){ - populateVersionInformation(remote.app.getVersion(), document.getElementById('settingsAboutCurrentVersionValue'), document.getElementById('settingsAboutCurrentVersionTitle'), document.getElementById('settingsAboutCurrentVersionCheck')) -} - -/** - * Fetches the GitHub atom release feed and parses it for the release notes - * of the current version. This value is displayed on the UI. - */ -function populateReleaseNotes(){ - $.ajax({ - url: 'https://github.com/dscalzi/HeliosLauncher/releases.atom', - success: (data) => { - const version = 'v' + remote.app.getVersion() - const entries = $(data).find('entry') - - for(let i=0; i { - settingsAboutChangelogText.innerHTML = 'Failed to load release notes.' - }) -} - -/** - * Prepare account tab for display. - */ -function prepareAboutTab(){ - populateAboutVersionInformation() - populateReleaseNotes() -} - -/** - * Update Tab - */ - -const settingsTabUpdate = document.getElementById('settingsTabUpdate') -const settingsUpdateTitle = document.getElementById('settingsUpdateTitle') -const settingsUpdateVersionCheck = document.getElementById('settingsUpdateVersionCheck') -const settingsUpdateVersionTitle = document.getElementById('settingsUpdateVersionTitle') -const settingsUpdateVersionValue = document.getElementById('settingsUpdateVersionValue') -const settingsUpdateChangelogTitle = settingsTabUpdate.getElementsByClassName('settingsChangelogTitle')[0] -const settingsUpdateChangelogText = settingsTabUpdate.getElementsByClassName('settingsChangelogText')[0] -const settingsUpdateChangelogCont = settingsTabUpdate.getElementsByClassName('settingsChangelogContainer')[0] -const settingsUpdateActionButton = document.getElementById('settingsUpdateActionButton') - -/** - * Update the properties of the update action button. - * - * @param {string} text The new button text. - * @param {boolean} disabled Optional. Disable or enable the button - * @param {function} handler Optional. New button event handler. - */ -function settingsUpdateButtonStatus(text, disabled = false, handler = null){ - settingsUpdateActionButton.innerHTML = text - settingsUpdateActionButton.disabled = disabled - if(handler != null){ - settingsUpdateActionButton.onclick = handler - } -} - -/** - * Populate the update tab with relevant information. - * - * @param {Object} data The update data. - */ -function populateSettingsUpdateInformation(data){ - if(data != null){ - settingsUpdateTitle.innerHTML = `New ${isPrerelease(data.version) ? 'Pre-release' : 'Release'} Available` - settingsUpdateChangelogCont.style.display = null - settingsUpdateChangelogTitle.innerHTML = data.releaseName - settingsUpdateChangelogText.innerHTML = data.releaseNotes - populateVersionInformation(data.version, settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck) - - if(process.platform === 'darwin'){ - settingsUpdateButtonStatus('Download from GitHubClose the launcher and run the dmg to update.', false, () => { - shell.openExternal(data.darwindownload) - }) - } else { - settingsUpdateButtonStatus('Downloading..', true) - } - } else { - settingsUpdateTitle.innerHTML = 'You Are Running the Latest Version' - settingsUpdateChangelogCont.style.display = 'none' - populateVersionInformation(remote.app.getVersion(), settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck) - settingsUpdateButtonStatus('Check for Updates', false, () => { - if(!isDev){ - ipcRenderer.send('autoUpdateAction', 'checkForUpdate') - settingsUpdateButtonStatus('Checking for Updates..', true) - } - }) - } -} - -/** - * Prepare update tab for display. - * - * @param {Object} data The update data. - */ -function prepareUpdateTab(data = null){ - populateSettingsUpdateInformation(data) -} - -/** - * Settings preparation functions. - */ - -/** - * Prepare the entire settings UI. - * - * @param {boolean} first Whether or not it is the first load. - */ -function prepareSettings(first = false) { - if(first){ - setupSettingsTabs() - initSettingsValidators() - prepareUpdateTab() - } else { - prepareModsTab() - } - initSettingsValues() - prepareAccountsTab() - prepareJavaTab() - prepareAboutTab() -} - -// Prepare the settings UI on startup. -//prepareSettings(true) diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js deleted file mode 100644 index 835e3c9d..00000000 --- a/app/assets/js/scripts/uibinder.js +++ /dev/null @@ -1,477 +0,0 @@ -/** - * Initialize UI functions which depend on internal modules. - * Loaded after core UI functions are initialized in uicore.js. - */ -// Requirements -const path = require('path') - -const AuthManager = require('./assets/js/authmanager') -const ConfigManager = require('./assets/js/configmanager') -const DistroManager = require('./assets/js/distromanager') -const Lang = require('./assets/js/langloader') - -let rscShouldLoad = false -let fatalStartupError = false - -// Mapping of each view to their container IDs. -const VIEWS = { - landing: '#landingContainer', - loginOptions: '#loginOptionsContainer', - login: '#loginContainer', - settings: '#settingsContainer', - welcome: '#welcomeContainer', - waiting: '#waitingContainer' -} - -// The currently shown view container. -let currentView - -/** - * Switch launcher views. - * - * @param {string} current The ID of the current view container. - * @param {*} next The ID of the next view container. - * @param {*} currentFadeTime Optional. The fade out time for the current view. - * @param {*} nextFadeTime Optional. The fade in time for the next view. - * @param {*} onCurrentFade Optional. Callback function to execute when the current - * view fades out. - * @param {*} onNextFade Optional. Callback function to execute when the next view - * fades in. - */ -function switchView(current, next, currentFadeTime = 500, nextFadeTime = 500, onCurrentFade = () => {}, onNextFade = () => {}){ - currentView = next - $(`${current}`).fadeOut(currentFadeTime, () => { - onCurrentFade() - $(`${next}`).fadeIn(nextFadeTime, () => { - onNextFade() - }) - }) -} - -/** - * Get the currently shown view container. - * - * @returns {string} The currently shown view container. - */ -function getCurrentView(){ - return currentView -} - -function showMainUI(data){ - - if(!isDev){ - loggerAutoUpdater.info('Initializing..') - ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease()) - } - - prepareSettings(true) - updateSelectedServer(data.getServer(ConfigManager.getSelectedServer())) - refreshServerStatus() - setTimeout(() => { - document.getElementById('frameBar').style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - document.body.style.backgroundImage = `url('assets/images/backgrounds/${document.body.getAttribute('bkid')}.jpg')` - $('#main').show() - - const isLoggedIn = Object.keys(ConfigManager.getAuthAccounts()).length > 0 - - // If this is enabled in a development environment we'll get ratelimited. - // The relaunch frequency is usually far too high. - if(!isDev && isLoggedIn){ - validateSelectedAccount() - } - - if(ConfigManager.isFirstLaunch()){ - currentView = VIEWS.welcome - $(VIEWS.welcome).fadeIn(1000) - } else { - if(isLoggedIn){ - currentView = VIEWS.landing - $(VIEWS.landing).fadeIn(1000) - } else { - loginOptionsCancelEnabled(false) - loginOptionsViewOnLoginSuccess = VIEWS.landing - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - currentView = VIEWS.loginOptions - $(VIEWS.loginOptions).fadeIn(1000) - } - } - - setTimeout(() => { - $('#loadingContainer').fadeOut(500, () => { - $('#loadSpinnerImage').removeClass('rotating') - }) - }, 250) - - }, 750) - // Disable tabbing to the news container. - initNews().then(() => { - $('#newsContainer *').attr('tabindex', '-1') - }) -} - -function showFatalStartupError(){ - setTimeout(() => { - $('#loadingContainer').fadeOut(250, () => { - document.getElementById('overlayContainer').style.background = 'none' - setOverlayContent( - 'Fatal Error: Unable to Load Distribution Index', - 'A connection could not be established to our servers to download the distribution index. No local copies were available to load.

The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application.', - 'Close' - ) - setOverlayHandler(() => { - const window = remote.getCurrentWindow() - window.close() - }) - toggleOverlay(true) - }) - }, 750) -} - -/** - * Common functions to perform after refreshing the distro index. - * - * @param {Object} data The distro index object. - */ -function onDistroRefresh(data){ - updateSelectedServer(data.getServer(ConfigManager.getSelectedServer())) - refreshServerStatus() - initNews() - syncModConfigurations(data) - ensureJavaSettings(data) -} - -/** - * Sync the mod configurations with the distro index. - * - * @param {Object} data The distro index object. - */ -function syncModConfigurations(data){ - - const syncedCfgs = [] - - for(let serv of data.getServers()){ - - const id = serv.getID() - const mdls = serv.getModules() - const cfg = ConfigManager.getModConfiguration(id) - - if(cfg != null){ - - const modsOld = cfg.mods - const mods = {} - - for(let mdl of mdls){ - const type = mdl.getType() - - if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){ - if(!mdl.getRequired().isRequired()){ - const mdlID = mdl.getVersionlessID() - if(modsOld[mdlID] == null){ - mods[mdlID] = scanOptionalSubModules(mdl.getSubModules(), mdl) - } else { - mods[mdlID] = mergeModConfiguration(modsOld[mdlID], scanOptionalSubModules(mdl.getSubModules(), mdl), false) - } - } else { - if(mdl.hasSubModules()){ - const mdlID = mdl.getVersionlessID() - const v = scanOptionalSubModules(mdl.getSubModules(), mdl) - if(typeof v === 'object'){ - if(modsOld[mdlID] == null){ - mods[mdlID] = v - } else { - mods[mdlID] = mergeModConfiguration(modsOld[mdlID], v, true) - } - } - } - } - } - } - - syncedCfgs.push({ - id, - mods - }) - - } else { - - const mods = {} - - for(let mdl of mdls){ - const type = mdl.getType() - if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){ - if(!mdl.getRequired().isRequired()){ - mods[mdl.getVersionlessID()] = scanOptionalSubModules(mdl.getSubModules(), mdl) - } else { - if(mdl.hasSubModules()){ - const v = scanOptionalSubModules(mdl.getSubModules(), mdl) - if(typeof v === 'object'){ - mods[mdl.getVersionlessID()] = v - } - } - } - } - } - - syncedCfgs.push({ - id, - mods - }) - - } - } - - ConfigManager.setModConfigurations(syncedCfgs) - ConfigManager.save() -} - -/** - * Ensure java configurations are present for the available servers. - * - * @param {Object} data The distro index object. - */ -function ensureJavaSettings(data) { - - // Nothing too fancy for now. - for(const serv of data.getServers()){ - ConfigManager.ensureJavaConfig(serv.getID(), serv.getMinecraftVersion()) - } - - ConfigManager.save() -} - -/** - * Recursively scan for optional sub modules. If none are found, - * this function returns a boolean. If optional sub modules do exist, - * a recursive configuration object is returned. - * - * @returns {boolean | Object} The resolved mod configuration. - */ -function scanOptionalSubModules(mdls, origin){ - if(mdls != null){ - const mods = {} - - for(let mdl of mdls){ - const type = mdl.getType() - // Optional types. - if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){ - // It is optional. - if(!mdl.getRequired().isRequired()){ - mods[mdl.getVersionlessID()] = scanOptionalSubModules(mdl.getSubModules(), mdl) - } else { - if(mdl.hasSubModules()){ - const v = scanOptionalSubModules(mdl.getSubModules(), mdl) - if(typeof v === 'object'){ - mods[mdl.getVersionlessID()] = v - } - } - } - } - } - - if(Object.keys(mods).length > 0){ - const ret = { - mods - } - if(!origin.getRequired().isRequired()){ - ret.value = origin.getRequired().isDefault() - } - return ret - } - } - return origin.getRequired().isDefault() -} - -/** - * Recursively merge an old configuration into a new configuration. - * - * @param {boolean | Object} o The old configuration value. - * @param {boolean | Object} n The new configuration value. - * @param {boolean} nReq If the new value is a required mod. - * - * @returns {boolean | Object} The merged configuration. - */ -function mergeModConfiguration(o, n, nReq = false){ - if(typeof o === 'boolean'){ - if(typeof n === 'boolean') return o - else if(typeof n === 'object'){ - if(!nReq){ - n.value = o - } - return n - } - } else if(typeof o === 'object'){ - if(typeof n === 'boolean') return typeof o.value !== 'undefined' ? o.value : true - else if(typeof n === 'object'){ - if(!nReq){ - n.value = typeof o.value !== 'undefined' ? o.value : true - } - - const newMods = Object.keys(n.mods) - for(let i=0; i${selectedAcc.displayName}. Please ${accLen > 0 ? 'select another account or ' : ''} login again.`, - 'Login', - 'Select Another Account' - ) - setOverlayHandler(() => { - - const isMicrosoft = selectedAcc.type === 'microsoft' - - if(isMicrosoft) { - // Empty for now - } else { - // Mojang - // For convenience, pre-populate the username of the account. - document.getElementById('loginUsername').value = selectedAcc.username - validateEmail(selectedAcc.username) - } - - loginOptionsViewOnLoginSuccess = getCurrentView() - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - - if(accLen > 0) { - loginOptionsViewOnCancel = getCurrentView() - loginOptionsViewCancelHandler = () => { - if(isMicrosoft) { - ConfigManager.addMicrosoftAuthAccount( - selectedAcc.uuid, - selectedAcc.accessToken, - selectedAcc.username, - selectedAcc.expiresAt, - selectedAcc.microsoft.access_token, - selectedAcc.microsoft.refresh_token, - selectedAcc.microsoft.expires_at - ) - } else { - ConfigManager.addMojangAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName) - } - ConfigManager.save() - validateSelectedAccount() - } - loginOptionsCancelEnabled(true) - } else { - loginOptionsCancelEnabled(false) - } - toggleOverlay(false) - switchView(getCurrentView(), VIEWS.loginOptions) - }) - setDismissHandler(() => { - if(accLen > 1){ - prepareAccountSelectionList() - $('#overlayContent').fadeOut(250, () => { - bindOverlayKeys(true, 'accountSelectContent', true) - $('#accountSelectContent').fadeIn(250) - }) - } else { - const accountsObj = ConfigManager.getAuthAccounts() - const accounts = Array.from(Object.keys(accountsObj), v => accountsObj[v]) - // This function validates the account switch. - setSelectedAccount(accounts[0].uuid) - toggleOverlay(false) - } - }) - toggleOverlay(true, accLen > 0) - } else { - return true - } - } else { - return true - } -} - -/** - * Temporary function to update the selected account along - * with the relevent UI elements. - * - * @param {string} uuid The UUID of the account. - */ -function setSelectedAccount(uuid){ - const authAcc = ConfigManager.setSelectedAccount(uuid) - ConfigManager.save() - updateSelectedAccount(authAcc) - validateSelectedAccount() -} - -// Synchronous Listener -document.addEventListener('readystatechange', function(){ - - if (document.readyState === 'interactive' || document.readyState === 'complete'){ - if(rscShouldLoad){ - rscShouldLoad = false - if(!fatalStartupError){ - const data = DistroManager.getDistribution() - showMainUI(data) - } else { - showFatalStartupError() - } - } - } - -}, false) - -// Actions that must be performed after the distribution index is downloaded. -ipcRenderer.on('distributionIndexDone', (event, res) => { - if(res) { - const data = DistroManager.getDistribution() - syncModConfigurations(data) - ensureJavaSettings(data) - if(document.readyState === 'interactive' || document.readyState === 'complete'){ - showMainUI(data) - } else { - rscShouldLoad = true - } - } else { - fatalStartupError = true - if(document.readyState === 'interactive' || document.readyState === 'complete'){ - showFatalStartupError() - } else { - rscShouldLoad = true - } - } -}) - -// Util for development -function devModeToggle() { - DistroManager.setDevMode(true) - DistroManager.pullLocal().then((data) => { - ensureJavaSettings(data) - updateSelectedServer(data.getServers()[0]) - syncModConfigurations(data) - }) -} diff --git a/app/assets/js/scripts/uicore.js b/app/assets/js/scripts/uicore.js deleted file mode 100644 index c35771ad..00000000 --- a/app/assets/js/scripts/uicore.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Core UI functions are initialized in this file. This prevents - * unexpected errors from breaking the core features. Specifically, - * actions in this file should not require the usage of any internal - * modules, excluding dependencies. - */ -// Requirements -const $ = require('jquery') -const {ipcRenderer, shell, webFrame} = require('electron') -const remote = require('@electron/remote') -const isDev = require('./assets/js/isdev') -const { LoggerUtil } = require('helios-core') - -const loggerUICore = LoggerUtil.getLogger('UICore') -const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater') - -// Log deprecation and process warnings. -process.traceProcessWarnings = true -process.traceDeprecation = true - -// Disable eval function. -// eslint-disable-next-line -window.eval = global.eval = function () { - throw new Error('Sorry, this app does not support window.eval().') -} - -// Display warning when devtools window is opened. -remote.getCurrentWebContents().on('devtools-opened', () => { - console.log('%cThe console is dark and full of terrors.', 'color: white; -webkit-text-stroke: 4px #a02d2a; font-size: 60px; font-weight: bold') - console.log('%cIf you\'ve been told to paste something here, you\'re being scammed.', 'font-size: 16px') - console.log('%cUnless you know exactly what you\'re doing, close this window.', 'font-size: 16px') -}) - -// Disable zoom, needed for darwin. -webFrame.setZoomLevel(0) -webFrame.setVisualZoomLevelLimits(1, 1) - -// Initialize auto updates in production environments. -let updateCheckListener -if(!isDev){ - ipcRenderer.on('autoUpdateNotification', (event, arg, info) => { - switch(arg){ - case 'checking-for-update': - loggerAutoUpdater.info('Checking for update..') - settingsUpdateButtonStatus('Checking for Updates..', true) - break - case 'update-available': - loggerAutoUpdater.info('New update available', info.version) - - if(process.platform === 'darwin'){ - info.darwindownload = `https://github.com/dscalzi/HeliosLauncher/releases/download/v${info.version}/Helios-Launcher-setup-${info.version}${process.arch === 'arm64' ? '-arm64' : '-x64'}.dmg` - showUpdateUI(info) - } - - populateSettingsUpdateInformation(info) - break - case 'update-downloaded': - loggerAutoUpdater.info('Update ' + info.version + ' ready to be installed.') - settingsUpdateButtonStatus('Install Now', false, () => { - if(!isDev){ - ipcRenderer.send('autoUpdateAction', 'installUpdateNow') - } - }) - showUpdateUI(info) - break - case 'update-not-available': - loggerAutoUpdater.info('No new update found.') - settingsUpdateButtonStatus('Check for Updates') - break - case 'ready': - updateCheckListener = setInterval(() => { - ipcRenderer.send('autoUpdateAction', 'checkForUpdate') - }, 1800000) - ipcRenderer.send('autoUpdateAction', 'checkForUpdate') - break - case 'realerror': - if(info != null && info.code != null){ - if(info.code === 'ERR_UPDATER_INVALID_RELEASE_FEED'){ - loggerAutoUpdater.info('No suitable releases found.') - } else if(info.code === 'ERR_XML_MISSED_ELEMENT'){ - loggerAutoUpdater.info('No releases found.') - } else { - loggerAutoUpdater.error('Error during update check..', info) - loggerAutoUpdater.debug('Error Code:', info.code) - } - } - break - default: - loggerAutoUpdater.info('Unknown argument', arg) - break - } - }) -} - -/** - * Send a notification to the main process changing the value of - * allowPrerelease. If we are running a prerelease version, then - * this will always be set to true, regardless of the current value - * of val. - * - * @param {boolean} val The new allow prerelease value. - */ -function changeAllowPrerelease(val){ - ipcRenderer.send('autoUpdateAction', 'allowPrereleaseChange', val) -} - -function showUpdateUI(info){ - //TODO Make this message a bit more informative `${info.version}` - document.getElementById('image_seal_container').setAttribute('update', true) - document.getElementById('image_seal_container').onclick = () => { - /*setOverlayContent('Update Available', 'A new update for the launcher is available. Would you like to install now?', 'Install', 'Later') - setOverlayHandler(() => { - if(!isDev){ - ipcRenderer.send('autoUpdateAction', 'installUpdateNow') - } else { - console.error('Cannot install updates in development environment.') - toggleOverlay(false) - } - }) - setDismissHandler(() => { - toggleOverlay(false) - }) - toggleOverlay(true, true)*/ - switchView(getCurrentView(), VIEWS.settings, 500, 500, () => { - settingsNavItemListener(document.getElementById('settingsNavUpdate'), false) - }) - } -} - -/* jQuery Example -$(function(){ - loggerUICore.info('UICore Initialized'); -})*/ - -document.addEventListener('readystatechange', function () { - if (document.readyState === 'interactive'){ - loggerUICore.info('UICore Initializing..') - - // Bind close button. - Array.from(document.getElementsByClassName('fCb')).map((val) => { - val.addEventListener('click', e => { - const window = remote.getCurrentWindow() - window.close() - }) - }) - - // Bind restore down button. - Array.from(document.getElementsByClassName('fRb')).map((val) => { - val.addEventListener('click', e => { - const window = remote.getCurrentWindow() - if(window.isMaximized()){ - window.unmaximize() - } else { - window.maximize() - } - document.activeElement.blur() - }) - }) - - // Bind minimize button. - Array.from(document.getElementsByClassName('fMb')).map((val) => { - val.addEventListener('click', e => { - const window = remote.getCurrentWindow() - window.minimize() - document.activeElement.blur() - }) - }) - - // Remove focus from social media buttons once they're clicked. - Array.from(document.getElementsByClassName('mediaURL')).map(val => { - val.addEventListener('click', e => { - document.activeElement.blur() - }) - }) - - } else if(document.readyState === 'complete'){ - - //266.01 - //170.8 - //53.21 - // Bind progress bar length to length of bot wrapper - //const targetWidth = document.getElementById("launch_content").getBoundingClientRect().width - //const targetWidth2 = document.getElementById("server_selection").getBoundingClientRect().width - //const targetWidth3 = document.getElementById("launch_button").getBoundingClientRect().width - - document.getElementById('launch_details').style.maxWidth = 266.01 - document.getElementById('launch_progress').style.width = 170.8 - document.getElementById('launch_details_right').style.maxWidth = 170.8 - document.getElementById('launch_progress_label').style.width = 53.21 - - } - -}, false) - -/** - * Open web links in the user's default browser. - */ -$(document).on('click', 'a[href^="http"]', function(event) { - event.preventDefault() - shell.openExternal(this.href) -}) - -/** - * Opens DevTools window if you hold (ctrl + shift + i). - * This will crash the program if you are using multiple - * DevTools, for example the chrome debugger in VS Code. - */ -document.addEventListener('keydown', function (e) { - if((e.key === 'I' || e.key === 'i') && e.ctrlKey && e.shiftKey){ - let window = remote.getCurrentWindow() - window.toggleDevTools() - } -}) \ No newline at end of file diff --git a/app/assets/js/scripts/welcome.js b/app/assets/js/scripts/welcome.js deleted file mode 100644 index ed0399c3..00000000 --- a/app/assets/js/scripts/welcome.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Script for welcome.ejs - */ -document.getElementById('welcomeButton').addEventListener('click', e => { - loginOptionsCancelEnabled(false) // False by default, be explicit. - loginOptionsViewOnLoginSuccess = VIEWS.landing - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - switchView(VIEWS.welcome, VIEWS.loginOptions) -}) \ No newline at end of file diff --git a/app/assets/js/serverstatus.js b/app/assets/js/serverstatus.js deleted file mode 100644 index 9729f9c7..00000000 --- a/app/assets/js/serverstatus.js +++ /dev/null @@ -1,65 +0,0 @@ -const net = require('net') - -/** - * Retrieves the status of a minecraft server. - * - * @param {string} address The server address. - * @param {number} port Optional. The port of the server. Defaults to 25565. - * @returns {Promise.} A promise which resolves to an object containing - * status information. - */ -exports.getStatus = function(address, port = 25565){ - - if(port == null || port == ''){ - port = 25565 - } - if(typeof port === 'string'){ - port = parseInt(port) - } - - return new Promise((resolve, reject) => { - const socket = net.connect(port, address, () => { - let buff = Buffer.from([0xFE, 0x01]) - socket.write(buff) - }) - - socket.setTimeout(2500, () => { - socket.end() - reject({ - code: 'ETIMEDOUT', - errno: 'ETIMEDOUT', - address, - port - }) - }) - - socket.on('data', (data) => { - if(data != null && data != ''){ - let server_info = data.toString().split('\x00\x00\x00') - const NUM_FIELDS = 6 - if(server_info != null && server_info.length >= NUM_FIELDS){ - resolve({ - online: true, - version: server_info[2].replace(/\u0000/g, ''), - motd: server_info[3].replace(/\u0000/g, ''), - onlinePlayers: server_info[4].replace(/\u0000/g, ''), - maxPlayers: server_info[5].replace(/\u0000/g,'') - }) - } else { - resolve({ - online: false - }) - } - } - socket.end() - }) - - socket.on('error', (err) => { - socket.destroy() - reject(err) - // ENOTFOUND = Unable to resolve. - // ECONNREFUSED = Unable to connect to port. - }) - }) - -} \ No newline at end of file diff --git a/app/assets/lang/en_US.json b/app/assets/lang/en_US.json deleted file mode 100644 index 25b34c24..00000000 --- a/app/assets/lang/en_US.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "html": { - "avatarOverlay": "Edit" - }, - "js": { - "login": { - "error": { - "invalidValue": "* Invalid Value", - "requiredValue": "* Required", - "userMigrated": { - "title": "Error During Login:
Invalid Credentials", - "desc": "You've attempted to login with a migrated account. Try again using the account email as the username." - }, - "invalidCredentials": { - "title": "Error During Login:
Invalid Credentials", - "desc": "The email or password you've entered is incorrect. Please try again." - }, - "rateLimit": { - "title": "Error During Login:
Too Many Attempts", - "desc": "There have been too many login attempts with this account recently. Please try again later." - }, - "noInternet": { - "title": "Error During Login:
No Internet Connection", - "desc": "You must be connected to the internet in order to login. Please connect and try again." - }, - "authDown": { - "title": "Error During Login:
Authentication Server Offline", - "desc": "Mojang's authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on Mojang's help portal." - }, - "notPaid": { - "title": "Error During Login:
Game Not Purchased", - "desc": "The account you are trying to login with has not purchased a copy of Minecraft.
You may purchase a copy on Minecraft.net" - }, - "unknown": { - "title": "Error During Login:
Unknown Error" - } - }, - "login": "LOGIN", - "loggingIn": "LOGGING IN", - "success": "SUCCESS", - "tryAgain": "Try Again" - }, - "landing": { - "launch": { - "pleaseWait": "Please wait.." - } - } - } -} \ No newline at end of file diff --git a/app/frame.ejs b/app/frame.ejs deleted file mode 100644 index c2aaf337..00000000 --- a/app/frame.ejs +++ /dev/null @@ -1,33 +0,0 @@ -
-
-
-
- <%if (process.platform === 'darwin') { %> -
-
- - - -
-
- <% } else{ %> -
-
- Helios Launcher -
-
- - - -
-
- <% } %> -
-
-
\ No newline at end of file diff --git a/app/landing.ejs b/app/landing.ejs deleted file mode 100644 index 7e747818..00000000 --- a/app/landing.ejs +++ /dev/null @@ -1,220 +0,0 @@ - \ No newline at end of file diff --git a/app/login.ejs b/app/login.ejs deleted file mode 100644 index 7ecc4a6c..00000000 --- a/app/login.ejs +++ /dev/null @@ -1,65 +0,0 @@ - \ No newline at end of file diff --git a/app/loginOptions.ejs b/app/loginOptions.ejs deleted file mode 100644 index 36af37e0..00000000 --- a/app/loginOptions.ejs +++ /dev/null @@ -1,34 +0,0 @@ - \ No newline at end of file diff --git a/app/overlay.ejs b/app/overlay.ejs deleted file mode 100644 index 0c18aef4..00000000 --- a/app/overlay.ejs +++ /dev/null @@ -1,41 +0,0 @@ - \ No newline at end of file diff --git a/app/settings.ejs b/app/settings.ejs deleted file mode 100644 index aa1fa764..00000000 --- a/app/settings.ejs +++ /dev/null @@ -1,393 +0,0 @@ - \ No newline at end of file diff --git a/app/waiting.ejs b/app/waiting.ejs deleted file mode 100644 index 11c7e4d2..00000000 --- a/app/waiting.ejs +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/app/welcome.ejs b/app/welcome.ejs deleted file mode 100644 index 077bbaed..00000000 --- a/app/welcome.ejs +++ /dev/null @@ -1,25 +0,0 @@ - \ No newline at end of file diff --git a/build/icon.png b/build/icon.png deleted file mode 100644 index 9e21d08b..00000000 Binary files a/build/icon.png and /dev/null differ diff --git a/dev-app-update.yml b/dev-app-update.yml deleted file mode 100644 index aa7ce8f4..00000000 --- a/dev-app-update.yml +++ /dev/null @@ -1,3 +0,0 @@ -owner: dscalzi -repo: HeliosLauncher -provider: github diff --git a/electron-builder.yml b/electron-builder.yml deleted file mode 100644 index cd5d1ce6..00000000 --- a/electron-builder.yml +++ /dev/null @@ -1,51 +0,0 @@ -appId: 'helioslauncher' -productName: 'Helios Launcher' -artifactName: '${productName}-setup-${version}.${ext}' - -copyright: 'Copyright © 2018-2022 Daniel Scalzi' - -asar: true -compression: 'maximum' - -files: - - '!{dist,.gitignore,.vscode,docs,dev-app-update.yml,.nvmrc,.eslintrc.json}' - -extraResources: - - 'libraries' - -# Windows Configuration -win: - target: - - target: 'nsis' - arch: 'x64' - -# Windows Installer Configuration -nsis: - oneClick: false - perMachine: false - allowElevation: true - allowToChangeInstallationDirectory: true - -# macOS Configuration -mac: - target: - - target: 'dmg' - arch: - - 'x64' - - 'arm64' - artifactName: '${productName}-setup-${version}-${arch}.${ext}' - category: 'public.app-category.games' - -# Linux Configuration -linux: - target: 'AppImage' - maintainer: 'Daniel Scalzi' - vendor: 'Daniel Scalzi' - synopsis: 'Modded Minecraft Launcher' - description: 'Custom launcher which allows users to join modded servers. All mods, configurations, and updates are handled automatically.' - category: 'Game' - - -directories: - buildResources: 'build' - output: 'dist' \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 24397812..00000000 --- a/index.js +++ /dev/null @@ -1,353 +0,0 @@ -const remoteMain = require('@electron/remote/main') -remoteMain.initialize() - -// Requirements -const { app, BrowserWindow, ipcMain, Menu, shell } = require('electron') -const autoUpdater = require('electron-updater').autoUpdater -const ejse = require('ejs-electron') -const fs = require('fs') -const isDev = require('./app/assets/js/isdev') -const path = require('path') -const semver = require('semver') -const { pathToFileURL } = require('url') -const { AZURE_CLIENT_ID, MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR, SHELL_OPCODE } = require('./app/assets/js/ipcconstants') - -// Setup auto updater. -function initAutoUpdater(event, data) { - - if(data){ - autoUpdater.allowPrerelease = true - } else { - // Defaults to true if application version contains prerelease components (e.g. 0.12.1-alpha.1) - // autoUpdater.allowPrerelease = true - } - - if(isDev){ - autoUpdater.autoInstallOnAppQuit = false - autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml') - } - if(process.platform === 'darwin'){ - autoUpdater.autoDownload = false - } - autoUpdater.on('update-available', (info) => { - event.sender.send('autoUpdateNotification', 'update-available', info) - }) - autoUpdater.on('update-downloaded', (info) => { - event.sender.send('autoUpdateNotification', 'update-downloaded', info) - }) - autoUpdater.on('update-not-available', (info) => { - event.sender.send('autoUpdateNotification', 'update-not-available', info) - }) - autoUpdater.on('checking-for-update', () => { - event.sender.send('autoUpdateNotification', 'checking-for-update') - }) - autoUpdater.on('error', (err) => { - event.sender.send('autoUpdateNotification', 'realerror', err) - }) -} - -// Open channel to listen for update actions. -ipcMain.on('autoUpdateAction', (event, arg, data) => { - switch(arg){ - case 'initAutoUpdater': - console.log('Initializing auto updater.') - initAutoUpdater(event, data) - event.sender.send('autoUpdateNotification', 'ready') - break - case 'checkForUpdate': - autoUpdater.checkForUpdates() - .catch(err => { - event.sender.send('autoUpdateNotification', 'realerror', err) - }) - break - case 'allowPrereleaseChange': - if(!data){ - const preRelComp = semver.prerelease(app.getVersion()) - if(preRelComp != null && preRelComp.length > 0){ - autoUpdater.allowPrerelease = true - } else { - autoUpdater.allowPrerelease = data - } - } else { - autoUpdater.allowPrerelease = data - } - break - case 'installUpdateNow': - autoUpdater.quitAndInstall() - break - default: - console.log('Unknown argument', arg) - break - } -}) -// Redirect distribution index event from preloader to renderer. -ipcMain.on('distributionIndexDone', (event, res) => { - event.sender.send('distributionIndexDone', res) -}) - -// Handle trash item. -ipcMain.handle(SHELL_OPCODE.TRASH_ITEM, async (event, ...args) => { - try { - await shell.trashItem(args[0]) - return { - result: true - } - } catch(error) { - return { - result: false, - error: error - } - } -}) - -// Disable hardware acceleration. -// https://electronjs.org/docs/tutorial/offscreen-rendering -app.disableHardwareAcceleration() - - -const REDIRECT_URI_PREFIX = 'https://login.microsoftonline.com/common/oauth2/nativeclient?' - -// Microsoft Auth Login -let msftAuthWindow -let msftAuthSuccess -let msftAuthViewSuccess -let msftAuthViewOnClose -ipcMain.on(MSFT_OPCODE.OPEN_LOGIN, (ipcEvent, ...arguments_) => { - if (msftAuthWindow) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN, msftAuthViewOnClose) - return - } - msftAuthSuccess = false - msftAuthViewSuccess = arguments_[0] - msftAuthViewOnClose = arguments_[1] - msftAuthWindow = new BrowserWindow({ - title: 'Microsoft Login', - backgroundColor: '#222222', - width: 520, - height: 600, - frame: true, - icon: getPlatformIcon('SealCircle') - }) - - msftAuthWindow.on('closed', () => { - msftAuthWindow = undefined - }) - - msftAuthWindow.on('close', () => { - if(!msftAuthSuccess) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED, msftAuthViewOnClose) - } - }) - - msftAuthWindow.webContents.on('did-navigate', (_, uri) => { - if (uri.startsWith(REDIRECT_URI_PREFIX)) { - let queries = uri.substring(REDIRECT_URI_PREFIX.length).split('#', 1).toString().split('&') - let queryMap = {} - - queries.forEach(query => { - const [name, value] = query.split('=') - queryMap[name] = decodeURI(value) - }) - - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.SUCCESS, queryMap, msftAuthViewSuccess) - - msftAuthSuccess = true - msftAuthWindow.close() - msftAuthWindow = null - } - }) - - msftAuthWindow.removeMenu() - msftAuthWindow.loadURL(`https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=select_account&client_id=${AZURE_CLIENT_ID}&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient`) -}) - -// Microsoft Auth Logout -let msftLogoutWindow -let msftLogoutSuccess -let msftLogoutSuccessSent -ipcMain.on(MSFT_OPCODE.OPEN_LOGOUT, (ipcEvent, uuid, isLastAccount) => { - if (msftLogoutWindow) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN) - return - } - - msftLogoutSuccess = false - msftLogoutSuccessSent = false - msftLogoutWindow = new BrowserWindow({ - title: 'Microsoft Logout', - backgroundColor: '#222222', - width: 520, - height: 600, - frame: true, - icon: getPlatformIcon('SealCircle') - }) - - msftLogoutWindow.on('closed', () => { - msftLogoutWindow = undefined - }) - - msftLogoutWindow.on('close', () => { - if(!msftLogoutSuccess) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED) - } else if(!msftLogoutSuccessSent) { - msftLogoutSuccessSent = true - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount) - } - }) - - msftLogoutWindow.webContents.on('did-navigate', (_, uri) => { - if(uri.startsWith('https://login.microsoftonline.com/common/oauth2/v2.0/logoutsession')) { - msftLogoutSuccess = true - setTimeout(() => { - if(!msftLogoutSuccessSent) { - msftLogoutSuccessSent = true - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount) - } - - if(msftLogoutWindow) { - msftLogoutWindow.close() - msftLogoutWindow = null - } - }, 5000) - } - }) - - msftLogoutWindow.removeMenu() - msftLogoutWindow.loadURL('https://login.microsoftonline.com/common/oauth2/v2.0/logout') -}) - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let win - -function createWindow() { - - win = new BrowserWindow({ - width: 980, - height: 552, - icon: getPlatformIcon('SealCircle'), - frame: false, - webPreferences: { - preload: path.join(__dirname, 'app', 'assets', 'js', 'preloader.js'), - nodeIntegration: true, - contextIsolation: false - }, - backgroundColor: '#171614' - }) - remoteMain.enable(win.webContents) - - ejse.data('bkid', Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length))) - - win.loadURL(pathToFileURL(path.join(__dirname, 'app', 'app.ejs')).toString()) - - /*win.once('ready-to-show', () => { - win.show() - })*/ - - win.removeMenu() - - win.resizable = true - - win.on('closed', () => { - win = null - }) -} - -function createMenu() { - - if(process.platform === 'darwin') { - - // Extend default included application menu to continue support for quit keyboard shortcut - let applicationSubMenu = { - label: 'Application', - submenu: [{ - label: 'About Application', - selector: 'orderFrontStandardAboutPanel:' - }, { - type: 'separator' - }, { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - app.quit() - } - }] - } - - // New edit menu adds support for text-editing keyboard shortcuts - let editSubMenu = { - label: 'Edit', - submenu: [{ - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - selector: 'undo:' - }, { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - selector: 'redo:' - }, { - type: 'separator' - }, { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - selector: 'cut:' - }, { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - selector: 'copy:' - }, { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - selector: 'paste:' - }, { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - selector: 'selectAll:' - }] - } - - // Bundle submenus into a single template and build a menu object with it - let menuTemplate = [applicationSubMenu, editSubMenu] - let menuObject = Menu.buildFromTemplate(menuTemplate) - - // Assign it to the application - Menu.setApplicationMenu(menuObject) - - } - -} - -function getPlatformIcon(filename){ - let ext - switch(process.platform) { - case 'win32': - ext = 'ico' - break - case 'darwin': - case 'linux': - default: - ext = 'png' - break - } - - return path.join(__dirname, 'app', 'assets', 'images', `${filename}.${ext}`) -} - -app.on('ready', createWindow) -app.on('ready', createMenu) - -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') { - 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 (win === null) { - createWindow() - } -}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 08645bef..00000000 --- a/package-lock.json +++ /dev/null @@ -1,4136 +0,0 @@ -{ - "name": "helioslauncher", - "version": "1.9.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "helioslauncher", - "version": "1.9.0", - "license": "UNLICENSED", - "dependencies": { - "@electron/remote": "^2.0.8", - "adm-zip": "^0.5.9", - "async": "^3.2.4", - "discord-rpc-patch": "^4.0.1", - "ejs": "^3.1.8", - "ejs-electron": "^2.1.1", - "electron-updater": "^5.3.0", - "fs-extra": "^11.1.0", - "github-syntax-dark": "^0.5.0", - "got": "^11.8.5", - "helios-core": "~0.1.2", - "jquery": "^3.6.1", - "node-disk-info": "^1.3.0", - "node-stream-zip": "^1.15.0", - "request": "^2.88.2", - "semver": "^7.3.8", - "tar-fs": "^2.1.1", - "winreg": "^1.2.4" - }, - "devDependencies": { - "electron": "^23.0.0", - "electron-builder": "^23.6.0", - "eslint": "^8.34.0" - }, - "engines": { - "node": "18.x.x" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@develar/schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", - "dev": true, - "dependencies": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/@electron/get": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz", - "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", - "progress": "^2.0.3", - "semver": "^6.2.0", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/@electron/get/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@electron/get/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@electron/get/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/@electron/remote": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.0.9.tgz", - "integrity": "sha512-LR0W0ID6WAKHaSs0x5LX9aiG+5pFBNAJL6eQAJfGkCuZPUa6nZz+czZLdlTDETG45CgF/0raSvCtYOYUpr6c+A==", - "peerDependencies": { - "electron": ">= 13.0.0" - } - }, - "node_modules/@electron/universal": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz", - "integrity": "sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==", - "dev": true, - "dependencies": { - "@malept/cross-spawn-promise": "^1.1.0", - "asar": "^3.1.0", - "debug": "^4.3.1", - "dir-compare": "^2.4.0", - "fs-extra": "^9.0.1", - "minimatch": "^3.0.4", - "plist": "^3.0.4" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/@electron/universal/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==", - "dev": true, - "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/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@malept/cross-spawn-promise": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", - "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/malept" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" - } - ], - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@malept/flatpak-bundler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", - "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.0", - "lodash": "^4.17.15", - "tmp-promise": "^3.0.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@malept/flatpak-bundler/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==", - "dev": true, - "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/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "optional": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "optional": true - }, - "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.12.tgz", - "integrity": "sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==" - }, - "node_modules/@types/plist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz", - "integrity": "sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*", - "xmlbuilder": ">=11.0.1" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" - }, - "node_modules/@types/triple-beam": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", - "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" - }, - "node_modules/@types/verror": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz", - "integrity": "sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ==", - "dev": true, - "optional": true - }, - "node_modules/@types/yargs": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/7zip-bin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz", - "integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/agent-base": { - "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, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/app-builder-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", - "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", - "dev": true - }, - "node_modules/app-builder-lib": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.6.0.tgz", - "integrity": "sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA==", - "dev": true, - "dependencies": { - "@develar/schema-utils": "~2.6.5", - "@electron/universal": "1.2.1", - "@malept/flatpak-bundler": "^0.4.0", - "7zip-bin": "~5.1.1", - "async-exit-hook": "^2.0.1", - "bluebird-lst": "^1.0.9", - "builder-util": "23.6.0", - "builder-util-runtime": "9.1.1", - "chromium-pickle-js": "^0.2.0", - "debug": "^4.3.4", - "ejs": "^3.1.7", - "electron-osx-sign": "^0.6.0", - "electron-publish": "23.6.0", - "form-data": "^4.0.0", - "fs-extra": "^10.1.0", - "hosted-git-info": "^4.1.0", - "is-ci": "^3.0.0", - "isbinaryfile": "^4.0.10", - "js-yaml": "^4.1.0", - "lazy-val": "^1.0.5", - "minimatch": "^3.1.2", - "read-config-file": "6.2.0", - "sanitize-filename": "^1.6.3", - "semver": "^7.3.7", - "tar": "^6.1.11", - "temp-file": "^3.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra": { - "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, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/asar": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz", - "integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==", - "deprecated": "Please use @electron/asar moving forward. There is no API change, just a package name change", - "dev": true, - "dependencies": { - "chromium-pickle-js": "^0.2.0", - "commander": "^5.0.0", - "glob": "^7.1.6", - "minimatch": "^3.0.4" - }, - "bin": { - "asar": "bin/asar.js" - }, - "engines": { - "node": ">=10.12.0" - }, - "optionalDependencies": { - "@types/glob": "^7.1.1" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "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, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/bluebird-lst": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", - "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5" - } - }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "optional": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "dev": true - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builder-util": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-23.6.0.tgz", - "integrity": "sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ==", - "dev": true, - "dependencies": { - "@types/debug": "^4.1.6", - "@types/fs-extra": "^9.0.11", - "7zip-bin": "~5.1.1", - "app-builder-bin": "4.0.0", - "bluebird-lst": "^1.0.9", - "builder-util-runtime": "9.1.1", - "chalk": "^4.1.1", - "cross-spawn": "^7.0.3", - "debug": "^4.3.4", - "fs-extra": "^10.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-ci": "^3.0.0", - "js-yaml": "^4.1.0", - "source-map-support": "^0.5.19", - "stat-mode": "^1.0.0", - "temp-file": "^3.4.0" - } - }, - "node_modules/builder-util-runtime": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", - "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", - "dependencies": { - "debug": "^4.3.4", - "sax": "^1.2.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/builder-util/node_modules/fs-extra": { - "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, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chromium-pickle-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", - "dev": true - }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "optional": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "node_modules/crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "optional": true, - "dependencies": { - "buffer": "^5.1.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "optional": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "optional": true - }, - "node_modules/dir-compare": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", - "integrity": "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==", - "dev": true, - "dependencies": { - "buffer-equal": "1.0.0", - "colors": "1.0.3", - "commander": "2.9.0", - "minimatch": "3.0.4" - }, - "bin": { - "dircompare": "src/cli/dircompare.js" - } - }, - "node_modules/dir-compare/node_modules/commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", - "dev": true, - "dependencies": { - "graceful-readlink": ">= 1.0.0" - }, - "engines": { - "node": ">= 0.6.x" - } - }, - "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/discord-rpc-patch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/discord-rpc-patch/-/discord-rpc-patch-4.0.1.tgz", - "integrity": "sha512-rnHZzNzUcSNdPZCRf18Nza6Nir4i3ljO0HIoSRadD0uEQwKa8PgqCf/tLUr/HJyJQ3NuXNHACMsOC6/JRhojmQ==", - "dependencies": { - "node-fetch": "^2.6.1", - "ws": "^7.3.1" - } - }, - "node_modules/dmg-builder": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.6.0.tgz", - "integrity": "sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==", - "dev": true, - "dependencies": { - "app-builder-lib": "23.6.0", - "builder-util": "23.6.0", - "builder-util-runtime": "9.1.1", - "fs-extra": "^10.0.0", - "iconv-lite": "^0.6.2", - "js-yaml": "^4.1.0" - }, - "optionalDependencies": { - "dmg-license": "^1.0.11" - } - }, - "node_modules/dmg-builder/node_modules/fs-extra": { - "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, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dmg-license": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", - "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "@types/plist": "^3.0.1", - "@types/verror": "^1.10.3", - "ajv": "^6.10.0", - "crc": "^3.8.0", - "iconv-corefoundation": "^1.1.7", - "plist": "^3.0.4", - "smart-buffer": "^4.0.2", - "verror": "^1.10.0" - }, - "bin": { - "dmg-license": "bin/dmg-license.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", - "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", - "dev": true - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ejs-electron": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ejs-electron/-/ejs-electron-2.1.1.tgz", - "integrity": "sha512-ShNZTsmDdHMu5MN6komfgdfTMS4NEi2GJY/e5Ncg5uiLoBIqmbEcRwLNsxwPXUH1JNQm4S/M+A77cjhccgWmdw==", - "dependencies": { - "ejs": "^3.1.3", - "mime": "^2.4.6" - } - }, - "node_modules/electron": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-23.0.0.tgz", - "integrity": "sha512-S6hVtTAjauMiiWP9sBVR5RpcUC464cNZ06I2EMUjeZBq+KooS6tLmNsfw0zLpAXDp1qosjlBP3v71NTZ3gd9iA==", - "hasInstallScript": true, - "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^16.11.26", - "extract-zip": "^2.0.1" - }, - "bin": { - "electron": "cli.js" - }, - "engines": { - "node": ">= 12.20.55" - } - }, - "node_modules/electron-builder": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-23.6.0.tgz", - "integrity": "sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw==", - "dev": true, - "dependencies": { - "@types/yargs": "^17.0.1", - "app-builder-lib": "23.6.0", - "builder-util": "23.6.0", - "builder-util-runtime": "9.1.1", - "chalk": "^4.1.1", - "dmg-builder": "23.6.0", - "fs-extra": "^10.0.0", - "is-ci": "^3.0.0", - "lazy-val": "^1.0.5", - "read-config-file": "6.2.0", - "simple-update-notifier": "^1.0.7", - "yargs": "^17.5.1" - }, - "bin": { - "electron-builder": "cli.js", - "install-app-deps": "install-app-deps.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/electron-builder/node_modules/fs-extra": { - "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, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-osx-sign": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz", - "integrity": "sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==", - "deprecated": "Please use @electron/osx-sign moving forward. Be aware the API is slightly different", - "dev": true, - "dependencies": { - "bluebird": "^3.5.0", - "compare-version": "^0.1.2", - "debug": "^2.6.8", - "isbinaryfile": "^3.0.2", - "minimist": "^1.2.0", - "plist": "^3.0.1" - }, - "bin": { - "electron-osx-flat": "bin/electron-osx-flat.js", - "electron-osx-sign": "bin/electron-osx-sign.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/electron-osx-sign/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/electron-osx-sign/node_modules/isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "dependencies": { - "buffer-alloc": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/electron-osx-sign/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/electron-publish": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-23.6.0.tgz", - "integrity": "sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg==", - "dev": true, - "dependencies": { - "@types/fs-extra": "^9.0.11", - "builder-util": "23.6.0", - "builder-util-runtime": "9.1.1", - "chalk": "^4.1.1", - "fs-extra": "^10.0.0", - "lazy-val": "^1.0.5", - "mime": "^2.5.2" - } - }, - "node_modules/electron-publish/node_modules/fs-extra": { - "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, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-updater": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-5.3.0.tgz", - "integrity": "sha512-iKEr7yQBcvnQUPnSDYGSWC9t0eF2YbZWeYYYZzYxdl+HiRejXFENjYMnYjoOm2zxyD6Cr2JTHZhp9pqxiXuCOw==", - "dependencies": { - "@types/semver": "^7.3.6", - "builder-util-runtime": "9.1.1", - "fs-extra": "^10.0.0", - "js-yaml": "^4.1.0", - "lazy-val": "^1.0.5", - "lodash.escaperegexp": "^4.1.2", - "lodash.isequal": "^4.5.0", - "semver": "^7.3.5", - "typed-emitter": "^2.1.0" - } - }, - "node_modules/electron-updater/node_modules/fs-extra": { - "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==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "optional": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "devOptional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", - "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "optional": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "optional": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/github-syntax-dark": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/github-syntax-dark/-/github-syntax-dark-0.5.0.tgz", - "integrity": "sha512-RoFaoOEmvX25k0FEV4PJ0y5McWZOI6ZKfFee7YzxKUzz2SErzFec9f2IpW2zbijzImx8s9Zsd5YvO01XxX7xVg==" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "optional": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "optional": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "optional": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/helios-core": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/helios-core/-/helios-core-0.1.2.tgz", - "integrity": "sha512-xM3+nZcymy9iS36Z5odmuFZAsrSuyviQUh+dffXc+utXOP/Ox7m2HUo76t86cN8R1tLjJ/OzQJLzILACVaRryg==", - "dependencies": { - "fs-extra": "^10.1.0", - "got": "^11.8.5", - "luxon": "^3.1.0", - "triple-beam": "^1.3.0", - "winston": "^3.8.2" - } - }, - "node_modules/helios-core/node_modules/fs-extra": { - "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==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/https-proxy-agent": { - "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, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-corefoundation": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", - "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "cli-truncate": "^2.1.0", - "node-addon-api": "^1.6.3" - }, - "engines": { - "node": "^8.11.2 || >=10" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jquery": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", - "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" - }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/jsprim/node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, - "node_modules/lazy-val": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/logform": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", - "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", - "dependencies": { - "@colors/colors": "1.5.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/luxon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", - "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", - "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true, - "optional": true - }, - "node_modules/node-disk-info": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-disk-info/-/node-disk-info-1.3.0.tgz", - "integrity": "sha512-NEx858vJZ0AoBtmD/ChBIHLjFTF28xCsDIgmFl4jtGKsvlUx9DU/OrMDjvj3qp/E4hzLN0HvTg7eJx5XFQvbeg==", - "dependencies": { - "iconv-lite": "^0.6.2" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "engines": { - "node": "*" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "node_modules/plist": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz", - "integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==", - "dev": true, - "dependencies": { - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-config-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", - "integrity": "sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg==", - "dev": true, - "dependencies": { - "dotenv": "^9.0.2", - "dotenv-expand": "^5.1.0", - "js-yaml": "^4.1.0", - "json5": "^2.2.0", - "lazy-val": "^1.0.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", - "optional": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-stable-stringify": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", - "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dev": true, - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "optional": true - }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "optional": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/shebang-command": { - "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, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "optional": true - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, - "node_modules/stat-mode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", - "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sumchecker": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", - "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "dependencies": { - "debug": "^4.1.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/temp-file": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", - "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", - "dev": true, - "dependencies": { - "async-exit-hook": "^2.0.1", - "fs-extra": "^10.0.0" - } - }, - "node_modules/temp-file/node_modules/fs-extra": { - "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, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp-promise": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", - "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", - "dev": true, - "dependencies": { - "tmp": "^0.2.0" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dev": true, - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "optional": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", - "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", - "optionalDependencies": { - "rxjs": "*" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/winreg": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", - "integrity": "sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA==" - }, - "node_modules/winston": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", - "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", - "dependencies": { - "@colors/colors": "1.5.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "dev": true, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/src/MicrosoftType.ts b/src/MicrosoftType.ts deleted file mode 100644 index ce06ad7b..00000000 --- a/src/MicrosoftType.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ConfigManager } from './manager/ConfigManager'; -// NOTE FOR THIRD-PARTY -// REPLACE THIS CLIENT ID WITH YOUR APPLICATION ID. -// SEE https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md -exports.AZURE_CLIENT_ID = ConfigManager.azureClientId; -// SEE NOTE ABOVE. - - -// Opcodes -export enum MSFT_OPCODE { - OPEN_LOGIN = 'MSFT_AUTH_OPEN_LOGIN', - OPEN_LOGOUT = 'MSFT_AUTH_OPEN_LOGOUT', - REPLY_LOGIN = 'MSFT_AUTH_REPLY_LOGIN', - REPLY_LOGOUT = 'MSFT_AUTH_REPLY_LOGOUT' -} -// Reply types for REPLY opcode. -export enum MSFT_REPLY_TYPE { - SUCCESS = 'MSFT_AUTH_REPLY_SUCCESS', - ERROR = 'MSFT_AUTH_REPLY_ERROR' -} -// Error types for ERROR reply. -export enum MSFT_ERROR { - ALREADY_OPEN = 'MSFT_AUTH_ERR_ALREADY_OPEN', - NOT_FINISHED = 'MSFT_AUTH_ERR_NOT_FINISHED' -} - -export enum SHELL_OPCODE { - TRASH_ITEM = 'TRASH_ITEM' -} \ No newline at end of file diff --git a/src/dto/Minecraft.ts b/src/frontend/dto/Minecraft.ts similarity index 100% rename from src/dto/Minecraft.ts rename to src/frontend/dto/Minecraft.ts diff --git a/src/dto/OpenJDKData.ts b/src/frontend/dto/OpenJDKData.ts similarity index 100% rename from src/dto/OpenJDKData.ts rename to src/frontend/dto/OpenJDKData.ts diff --git a/src/lang/en_US.json b/src/lang/en_US.json deleted file mode 100644 index 25b34c24..00000000 --- a/src/lang/en_US.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "html": { - "avatarOverlay": "Edit" - }, - "js": { - "login": { - "error": { - "invalidValue": "* Invalid Value", - "requiredValue": "* Required", - "userMigrated": { - "title": "Error During Login:
Invalid Credentials", - "desc": "You've attempted to login with a migrated account. Try again using the account email as the username." - }, - "invalidCredentials": { - "title": "Error During Login:
Invalid Credentials", - "desc": "The email or password you've entered is incorrect. Please try again." - }, - "rateLimit": { - "title": "Error During Login:
Too Many Attempts", - "desc": "There have been too many login attempts with this account recently. Please try again later." - }, - "noInternet": { - "title": "Error During Login:
No Internet Connection", - "desc": "You must be connected to the internet in order to login. Please connect and try again." - }, - "authDown": { - "title": "Error During Login:
Authentication Server Offline", - "desc": "Mojang's authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on Mojang's help portal." - }, - "notPaid": { - "title": "Error During Login:
Game Not Purchased", - "desc": "The account you are trying to login with has not purchased a copy of Minecraft.
You may purchase a copy on Minecraft.net" - }, - "unknown": { - "title": "Error During Login:
Unknown Error" - } - }, - "login": "LOGIN", - "loggingIn": "LOGGING IN", - "success": "SUCCESS", - "tryAgain": "Try Again" - }, - "landing": { - "launch": { - "pleaseWait": "Please wait.." - } - } - } -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 27a33b03..00000000 --- a/src/main.ts +++ /dev/null @@ -1,343 +0,0 @@ -import remoteMain from "@electron/remote/main"; -import { app, ipcMain, Menu, MenuItem, shell } from "electron"; -import { autoUpdater } from "electron-updater"; -import { join } from "path"; -import { prerelease } from "semver"; -import { DevUtil } from "./util/DevUtil"; -import { MSFT_ERROR, MSFT_OPCODE, MSFT_REPLY_TYPE, SHELL_OPCODE } from './MicrosoftType'; -import { BrowserWindow } from "@electron/remote"; -import { readdirSync } from "fs-extra"; -import { pathToFileURL } from "url"; -import { data } from "ejs-electron"; -import { ConfigManager } from "./manager/ConfigManager"; - -remoteMain.initialize(); -// Setup auto updater. -function initAutoUpdater(event, data) { - - if (data) { - autoUpdater.allowPrerelease = true - } else { - // Defaults to true if application version contains prerelease components (e.g. 0.12.1-alpha.1) - // autoUpdater.allowPrerelease = true - } - - if (DevUtil.IsDev) { - autoUpdater.autoInstallOnAppQuit = false - autoUpdater.updateConfigPath = join(__dirname, 'dev-app-update.yml') - } - if (process.platform === 'darwin') { - autoUpdater.autoDownload = false - } - autoUpdater.on('update-available', (info) => { - event.sender.send('autoUpdateNotification', 'update-available', info) - }) - autoUpdater.on('update-downloaded', (info) => { - event.sender.send('autoUpdateNotification', 'update-downloaded', info) - }) - autoUpdater.on('update-not-available', (info) => { - event.sender.send('autoUpdateNotification', 'update-not-available', info) - }) - autoUpdater.on('checking-for-update', () => { - event.sender.send('autoUpdateNotification', 'checking-for-update') - }) - autoUpdater.on('error', (err) => { - event.sender.send('autoUpdateNotification', 'realerror', err) - }) -} - -// Open channel to listen for update actions. -ipcMain.on('autoUpdateAction', (event, arg, data) => { - switch (arg) { - case 'initAutoUpdater': - console.log('Initializing auto updater.') - initAutoUpdater(event, data) - event.sender.send('autoUpdateNotification', 'ready') - break - case 'checkForUpdate': - autoUpdater.checkForUpdates() - .catch(err => { - event.sender.send('autoUpdateNotification', 'realerror', err) - }) - break - case 'allowPrereleaseChange': - if (!data) { - const preRelComp = prerelease(app.getVersion()) - if (preRelComp != null && preRelComp.length > 0) { - autoUpdater.allowPrerelease = true - } else { - autoUpdater.allowPrerelease = data - } - } else { - autoUpdater.allowPrerelease = data - } - break - case 'installUpdateNow': - autoUpdater.quitAndInstall() - break - default: - console.log('Unknown argument', arg) - break - } -}); - -// Redirect distribution index event from preloader to renderer. -ipcMain.on('distributionIndexDone', (event, res) => { - event.sender.send('distributionIndexDone', res) -}) - -// Handle trash item. -ipcMain.handle(SHELL_OPCODE.TRASH_ITEM, async (event, ...args) => { - try { - await shell.trashItem(args[0]) - return { - result: true - } - } catch (error) { - return { - result: false, - error: error - } - } -}) - - -const REDIRECT_URI_PREFIX = 'https://login.microsoftonline.com/common/oauth2/nativeclient?' - -// Microsoft Auth Login -let msftAuthWindow -let msftAuthSuccess -let msftAuthViewSuccess -let msftAuthViewOnClose -ipcMain.on(MSFT_OPCODE.OPEN_LOGIN, (ipcEvent, ...arguments_) => { - if (msftAuthWindow) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN, msftAuthViewOnClose) - return - } - msftAuthSuccess = false - msftAuthViewSuccess = arguments_[0] - msftAuthViewOnClose = arguments_[1] - msftAuthWindow = new BrowserWindow({ - title: 'Microsoft Login', - backgroundColor: '#222222', - width: 520, - height: 600, - frame: true, - icon: getPlatformIcon('SealCircle') - }) - - msftAuthWindow.on('closed', () => { - msftAuthWindow = undefined - }) - - msftAuthWindow.on('close', () => { - if (!msftAuthSuccess) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED, msftAuthViewOnClose) - } - }) - - msftAuthWindow.webContents.on('did-navigate', (_, uri) => { - if (uri.startsWith(REDIRECT_URI_PREFIX)) { - let queries = uri.substring(REDIRECT_URI_PREFIX.length).split('#', 1).toString().split('&') - let queryMap = {} - - queries.forEach(query => { - const [name, value] = query.split('=') - queryMap[name] = decodeURI(value) - }) - - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.SUCCESS, queryMap, msftAuthViewSuccess) - - msftAuthSuccess = true - msftAuthWindow.close() - msftAuthWindow = null - } - }) - - msftAuthWindow.removeMenu() - msftAuthWindow.loadURL(`https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=select_account&client_id=${ConfigManager.azureClientId}&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient`) -}) - -// Microsoft Auth Logout -let msftLogoutWindow -let msftLogoutSuccess -let msftLogoutSuccessSent -ipcMain.on(MSFT_OPCODE.OPEN_LOGOUT, (ipcEvent, uuid, isLastAccount) => { - if (msftLogoutWindow) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN) - return - } - - msftLogoutSuccess = false - msftLogoutSuccessSent = false - msftLogoutWindow = new BrowserWindow({ - title: 'Microsoft Logout', - backgroundColor: '#222222', - width: 520, - height: 600, - frame: true, - icon: getPlatformIcon('SealCircle') - }) - - msftLogoutWindow.on('closed', () => { - msftLogoutWindow = undefined - }) - - msftLogoutWindow.on('close', () => { - if (!msftLogoutSuccess) { - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED) - } else if (!msftLogoutSuccessSent) { - msftLogoutSuccessSent = true - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount) - } - }) - - msftLogoutWindow.webContents.on('did-navigate', (_, uri) => { - if (uri.startsWith('https://login.microsoftonline.com/common/oauth2/v2.0/logoutsession')) { - msftLogoutSuccess = true - setTimeout(() => { - if (!msftLogoutSuccessSent) { - msftLogoutSuccessSent = true - ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount) - } - - if (msftLogoutWindow) { - msftLogoutWindow.close() - msftLogoutWindow = null - } - }, 5000) - } - }) - - msftLogoutWindow.removeMenu() - msftLogoutWindow.loadURL('https://login.microsoftonline.com/common/oauth2/v2.0/logout') -}) - - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let win - -function createWindow() { - - win = new BrowserWindow({ - width: 980, - height: 552, - icon: getPlatformIcon('SealCircle'), - frame: false, - webPreferences: { - preload: join(__dirname, 'app', 'assets', 'js', 'preloader.js'), - nodeIntegration: true, - contextIsolation: false - }, - backgroundColor: '#171614' - }) - remoteMain.enable(win.webContents) - - data('bkid', Math.floor((Math.random() * readdirSync(join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length))) - - win.loadURL(pathToFileURL(join(__dirname, 'app', 'app.ejs')).toString()) - - /*win.once('ready-to-show', () => { - win.show() - })*/ - - win.removeMenu() - - win.resizable = true - - win.on('closed', () => { - win = null - }) -} - -function createMenu() { - if (process.platform === 'darwin') { - // Extend default included application menu to continue support for quit keyboard shortcut - let applicationSubMenu = new MenuItem({ - label: 'Application', - submenu: [{ - label: 'About Application', - }, { - type: 'separator' - }, { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - app.quit() - } - }] - }) - - // New edit menu adds support for text-editing keyboard shortcuts - let editSubMenu = new MenuItem({ - label: "Edit", - submenu: [{ - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - }, { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - }, { - type: 'separator' - }, { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - }, { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - }, { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - }, { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - }] - }) - - // Bundle submenus into a single template and build a menu object with it - - let menuTemplate = [applicationSubMenu, editSubMenu] - let menuObject = Menu.buildFromTemplate(menuTemplate) - - // Assign it to the application - Menu.setApplicationMenu(menuObject) - - } - -} - -function getPlatformIcon(filename) { - let ext - switch (process.platform) { - case 'win32': - ext = 'ico' - break - case 'darwin': - case 'linux': - default: - ext = 'png' - break - } - - return join(__dirname, 'app', 'assets', 'images', `${filename}.${ext}`) -} - -app.on('ready', createWindow) -app.on('ready', createMenu) - -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') { - 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 (win === null) { - createWindow() - } -}) \ No newline at end of file diff --git a/src/manager/ConfigManager.ts b/src/manager/ConfigManager.ts deleted file mode 100644 index 71f7996f..00000000 --- a/src/manager/ConfigManager.ts +++ /dev/null @@ -1,825 +0,0 @@ -import { ensureDirSync, moveSync } from "fs-extra"; -import { LoggerUtil } from "helios-core/."; -import { existsSync, readFileSync, writeFileSync } from "fs-extra"; -import os from 'os'; -import { join } from 'path'; -import { MinecraftUtil } from "../util/MinecraftUtil"; -import { resolveMaxRAM, resolveMinRAM } from "../util/System"; - -const logger = LoggerUtil.getLogger("ConfigManager"); - -type Config = { - settings: { - game: { - resWidth: number, - resHeight: number, - fullscreen: boolean, - autoConnect: boolean, - launchDetached: boolean, - }, - launcher: { - allowPrerelease: boolean, - dataDirectory: string, - }, - - }, - newsCache: { - date?: any, - content?: any, - dismissed: boolean, - }, - clientToken?: string, - selectedServer?: any, // Resolved - selectedAccount?: any, - authenticationDatabase: any, - modConfigurations: any[], - javaConfig: any, -} - - -export class ConfigManager { - - private static sysRoot = process.env.APPDATA ?? (process.platform == "darwin" ? process.env.HOME + "/Library/Application Support" : process.env.HOME) ?? ""; - // TODO change - private static dataPath = join(this.sysRoot, ".randomia"); - private static configPath = join(exports.getLauncherDirectory(), "config.json"); - private static configPathLEGACY = join(this.dataPath, "config.json"); - private static firstLaunch = !existsSync(this.configPath) && !existsSync(this.configPathLEGACY); - // Forked processes do not have access to electron, so we have this workaround. - private static launcherDir = process.env.CONFIG_DIRECT_PATH ?? require("@electron/remote").app.getPath("userData"); - public static readonly DistributionURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'; - public static readonly launcherName = 'Helios-Launcher' - public static readonly azureClientId = '1ce6e35a-126f-48fd-97fb-54d143ac6d45' - /** - * Three types of values: - * Static = Explicitly declared. - * Dynamic = Calculated by a private static function. - * Resolved = Resolved externally, defaults to null. - */ - private static DEFAULT_CONFIG: Config = { - settings: { - game: { - resWidth: 1280, - resHeight: 720, - fullscreen: false, - autoConnect: true, - launchDetached: true, - }, - launcher: { - allowPrerelease: false, - dataDirectory: this.dataPath, - }, - }, - newsCache: { - date: undefined, - content: undefined, - dismissed: false, - }, - clientToken: undefined, - selectedServer: undefined, // Resolved - selectedAccount: undefined, - authenticationDatabase: {}, - modConfigurations: [], - javaConfig: {}, - } - - private static config: Config; - - /** - * Retrieve the absolute path of the launcher directory. - * - * @returns {string} The absolute path of the launcher directory. - */ - public static getLauncherDirectory() { - return this.launcherDir; - }; - - /** - * Get the launcher's data directory. This is where all files related - * to game launch are installed (common, instances, java, etc). - * - * @returns {string} The absolute path of the launcher's data directory. - */ - public static getDataDirectory(def = false) { - return !def ? this.config.settings.launcher.dataDirectory : this.DEFAULT_CONFIG.settings.launcher.dataDirectory; - }; - - /** - * Set the new data directory. - * - * @param {string} dataDirectory The new data directory. - */ - public static setDataDirectory(dataDirectory: string) { - this.config.settings.launcher.dataDirectory = dataDirectory; - }; - - - - public static getAbsoluteMinRAM() { - const mem = os.totalmem(); - return mem >= 6000000000 ? 3 : 2; - }; - - public static getAbsoluteMaxRAM() { - const mem = os.totalmem(); - const gT16 = mem - 16000000000; - return Math.floor((mem - 1000000000 - (gT16 > 0 ? gT16 / 8 + 16000000000 / 4 : mem / 4)) / 1000000000); - }; - - /** - * Save the current configuration to a file. - */ - public static save() { - writeFileSync(this.configPath, JSON.stringify(this.config, null, 4), { encoding: "utf-8" }); - }; - - /** - * Load the configuration into memory. If a configuration file exists, - * that will be read and saved. Otherwise, a default configuration will - * be generated. Note that "resolved" values default to null and will - * need to be externally assigned. - */ - public static load() { - let doLoad = true; - - if (!existsSync(this.configPath)) { - // Create all parent directories. - ensureDirSync(join(this.configPath, "..")); - if (existsSync(this.configPathLEGACY)) { - moveSync(this.configPathLEGACY, this.configPath); - } else { - doLoad = false; - this.config = this.DEFAULT_CONFIG; - exports.save(); - } - } - if (doLoad) { - let doValidate = false; - try { - this.config = JSON.parse(readFileSync(this.configPath, { encoding: "utf-8" })); - doValidate = true; - } catch (err) { - logger.error(err); - logger.info("Configuration file contains malformed JSON or is corrupt."); - logger.info("Generating a new configuration file."); - ensureDirSync(join(this.configPath, "..")); - this.config = this.DEFAULT_CONFIG; - exports.save(); - } - if (doValidate) { - this.config = this.validateKeySet(this.DEFAULT_CONFIG, this.config); - exports.save(); - } - } - logger.info("Successfully Loaded"); - }; - - /** - * @returns {boolean} Whether or not the manager has been loaded. - */ - public static get isLoaded() { - return this.config != null; - }; - - - - /** - * Check to see if this is the first time the user has launched the - * application. This is determined by the existance of the data - * - * @returns {boolean} True if this is the first launch, otherwise false. - */ - public static get isFirstLaunch() { - return this.firstLaunch; - }; - - /** - * Returns the name of the folder in the OS temp directory which we - * will use to extract and store native dependencies for game launch. - * - * @returns {string} The name of the folder. - */ - public static get tempNativeFolder() { - return "WCNatives"; - }; - - // System Settings (Unconfigurable on UI) - - /** - * Retrieve the news cache to determine - * whether or not there is newer news. - * - * @returns {Object} The news cache object. - */ - public static get getNewsCache() { - return this.config.newsCache; - }; - - /** - * Set the new news cache object. - * - * @param {Object} newsCache The new news cache object. - */ - public static setNewsCache(newsCache: { - date?: any; - content?: any; - dismissed: boolean; - }) { - this.config.newsCache = newsCache; - }; - - /** - * Set whether or not the news has been dismissed (checked) - * - * @param {boolean} dismissed Whether or not the news has been dismissed (checked). - */ - public static setNewsCacheDismissed(dismissed: boolean) { - this.config.newsCache.dismissed = dismissed; - }; - - /** - * Retrieve the common directory for shared - * game files (assets, libraries, etc). - * - * @returns {string} The launcher's common directory. - */ - public static get commonDirectory() { - return join(exports.getDataDirectory(), "common"); - }; - - /** - * Retrieve the instance directory for the per - * server game directories. - * - * @returns {string} The launcher's instance directory. - */ - public static get instanceDirectory() { - return join(exports.getDataDirectory(), "instances"); - }; - - /** - * Retrieve the launcher's Client Token. - * There is no default client token. - * - * @returns {string} The launcher's Client Token. - */ - public static get clientToken() { - return this.config.clientToken; - }; - - /** - * Set the launcher's Client Token. - * - * @param {string} clientToken The launcher's new Client Token. - */ - public static set clientToken(clientToken) { - this.config.clientToken = clientToken; - }; - - /** - * Retrieve the ID of the selected serverpack. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {string} The ID of the selected serverpack. - */ - public static getSelectedServer(def = false) { - return !def ? this.config.selectedServer : this.DEFAULT_CONFIG.clientToken; - }; - - /** - * Set the ID of the selected serverpack. - * - * @param {string} serverID The ID of the new selected serverpack. - */ - public static set selectedServer(serverID: string) { - this.config.selectedServer = serverID; - }; - - /** - * Get an array of each account currently authenticated by the launcher. - * - * @returns {Array.} An array of each stored authenticated account. - */ - public static get authAccounts() { - return this.config.authenticationDatabase; - }; - - /** - * Returns the authenticated account with the given uuid. Value may - * be null. - * - * @param {string} uuid The uuid of the authenticated account. - * @returns {Object} The authenticated account with the given uuid. - */ - public static getAuthAccountByUuid(uuid: string) { - return this.config.authenticationDatabase[uuid]; - }; - - /** - * Update the access token of an authenticated mojang account. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The new Access Token. - * - * @returns {Object} The authenticated account object created by this action. - */ - public static updateMojangAuthAccount(uuid: string, accessToken: string) { - this.config.authenticationDatabase[uuid].accessToken = accessToken; - this.config.authenticationDatabase[uuid].type = "mojang"; // For gradual conversion. - return this.config.authenticationDatabase[uuid]; - }; - - /** - * Adds an authenticated mojang account to the database to be stored. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The accessToken of the authenticated account. - * @param {string} username The username (usually email) of the authenticated account. - * @param {string} displayName The in game name of the authenticated account. - * - * @returns {Object} The authenticated account object created by this action. - */ - public static addMojangAuthAccount(uuid: string, accessToken: string, username: string, displayName: string) { - this.config.selectedAccount = uuid; - this.config.authenticationDatabase[uuid] = { - type: "mojang", - accessToken, - username: username.trim(), - uuid: uuid.trim(), - displayName: displayName.trim(), - }; - return this.config.authenticationDatabase[uuid]; - }; - - /** - * Update the tokens of an authenticated microsoft account. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The new Access Token. - * @param {string} msAccessToken The new Microsoft Access Token - * @param {string} msRefreshToken The new Microsoft Refresh Token - * @param {date} msExpires The date when the microsoft access token expires - * @param {date} mcExpires The date when the mojang access token expires - * - * @returns {Object} The authenticated account object created by this action. - */ - public static updateMicrosoftAuthAccount(uuid: string, accessToken: string, msAccessToken: string, msRefreshToken: string, msExpires: string, mcExpires: string) { - this.config.authenticationDatabase[uuid].accessToken = accessToken; - this.config.authenticationDatabase[uuid].expiresAt = mcExpires; - this.config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken; - this.config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken; - this.config.authenticationDatabase[uuid].microsoft.expires_at = msExpires; - return this.config.authenticationDatabase[uuid]; - }; - - /** - * Adds an authenticated microsoft account to the database to be stored. - * - * @param {string} uuid The uuid of the authenticated account. - * @param {string} accessToken The accessToken of the authenticated account. - * @param {string} name The in game name of the authenticated account. - * @param {date} mcExpires The date when the mojang access token expires - * @param {string} msAccessToken The microsoft access token - * @param {string} msRefreshToken The microsoft refresh token - * @param {date} msExpires The date when the microsoft access token expires - * - * @returns {Object} The authenticated account object created by this action. - */ - public static addMicrosoftAuthAccount( - uuid: string, - accessToken: string, - name: string, - mcExpires: string, - msAccessToken: string, - msRefreshToken: string, - msExpires: string - ) { - this.config.selectedAccount = uuid; - this.config.authenticationDatabase[uuid] = { - type: "microsoft", - accessToken, - username: name.trim(), - uuid: uuid.trim(), - displayName: name.trim(), - expiresAt: mcExpires, - microsoft: { - access_token: msAccessToken, - refresh_token: msRefreshToken, - expires_at: msExpires, - }, - }; - return this.config.authenticationDatabase[uuid]; - }; - - /** - * Remove an authenticated account from the database. If the account - * was also the selected account, a new one will be selected. If there - * are no accounts, the selected account will be null. - * - * @param {string} uuid The uuid of the authenticated account. - * - * @returns {boolean} True if the account was removed, false if it never existed. - */ - public static removeAuthAccount(uuid: string) { - if (this.config.authenticationDatabase[uuid] != null) { - delete this.config.authenticationDatabase[uuid]; - if (this.config.selectedAccount === uuid) { - const keys = Object.keys(this.config.authenticationDatabase); - if (keys.length > 0) { - this.config.selectedAccount = keys[0]; - } else { - this.config.selectedAccount = null; - this.config.clientToken = undefined; - } - } - return true; - } - return false; - }; - - /** - * Get the currently selected authenticated account. - * - * @returns {Object} The selected authenticated account. - */ - public static getSelectedAccount() { - return this.config.authenticationDatabase[this.config.selectedAccount]; - }; - - /** - * Set the selected authenticated account. - * - * @param {string} uuid The UUID of the account which is to be set - * as the selected account. - * - * @returns {Object} The selected authenticated account. - */ - public static setSelectedAccount(uuid: string) { - const authAcc = this.config.authenticationDatabase[uuid]; - if (authAcc != null) { - this.config.selectedAccount = uuid; - } - return authAcc; - }; - - /** - * Get an array of each mod configuration currently stored. - * - * @returns {Array.} An array of each stored mod configuration. - */ - public static get modConfigurations() { - return this.config.modConfigurations; - }; - - /** - * Set the array of stored mod configurations. - * - * @param {Array.} configurations An array of mod configurations. - */ - public static set modConfigurations(configurations) { - this.config.modConfigurations = configurations; - }; - - /** - * Get the mod configuration for a specific server. - * - * @param {string} serverid The id of the server. - * @returns {Object} The mod configuration for the given server. - */ - public static getModConfigurationForServer(serverid: string) { - const cfgs = this.config.modConfigurations; - for (let i = 0; i < cfgs.length; i++) { - if (cfgs[i].id === serverid) { - return cfgs[i]; - } - } - return null; - }; - - /** - * Set the mod configuration for a specific server. This overrides any existing value. - * - * @param {string} serverid The id of the server for the given mod configuration. - * @param {Object} configuration The mod configuration for the given server. - */ - public static setModConfigurationForServer(serverid: string, configuration) { - const cfgs = this.config.modConfigurations; - for (let i = 0; i < cfgs.length; i++) { - if (cfgs[i].id === serverid) { - cfgs[i] = configuration; - return; - } - } - cfgs.push(configuration); - }; - - - ///////////////////////////////////// JAVA CONFIG //////////////////////////////////////////// - - - // User Configurable Settings - - // Java Settings - - /** - * Ensure a java config property is set for the given server. - * - * @param {string} serverid The server id. - * @param {*} mcVersion The minecraft version of the server. - */ - public static ensureJavaConfig(serverid: string, mcVersion: string) { - if (!Object.prototype.hasOwnProperty.call(this.config.javaConfig, serverid)) { - this.config.javaConfig[serverid] = this.defaultJavaConfig(mcVersion); - } - }; - - /** - * Retrieve the minimum amount of memory for JVM initialization. This value - * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = - * 1024 MegaBytes, etc. - * - * @param {string} serverid The server id. - * @returns {string} The minimum amount of memory for JVM initialization. - */ - public static getMinRAM(serverid: string) { - return this.config.javaConfig[serverid].minRAM; - }; - - /** - * Set the minimum amount of memory for JVM initialization. This value should - * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = - * 1024 MegaBytes, etc. - * - * @param {string} serverid The server id. - * @param {string} minRAM The new minimum amount of memory for JVM initialization. - */ - public static setMinRAM(serverid: string, minRAM: string) { - this.config.javaConfig[serverid].minRAM = minRAM; - }; - - /** - * Retrieve the maximum amount of memory for JVM initialization. This value - * contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = - * 1024 MegaBytes, etc. - * - * @param {string} serverid The server id. - * @returns {string} The maximum amount of memory for JVM initialization. - */ - public static getMaxRAM(serverid: string) { - return this.config.javaConfig[serverid].maxRAM; - }; - - /** - * Set the maximum amount of memory for JVM initialization. This value should - * contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' = - * 1024 MegaBytes, etc. - * - * @param {string} serverid The server id. - * @param {string} maxRAM The new maximum amount of memory for JVM initialization. - */ - public static setMaxRAM(serverid: string, maxRAM: string) { - this.config.javaConfig[serverid].maxRAM = maxRAM; - }; - - /** - * Retrieve the path of the Java Executable. - * - * This is a resolved configuration value and defaults to null until externally assigned. - * - * @param {string} serverid The server id. - * @returns {string} The path of the Java Executable. - */ - public static getJavaExecutable(serverid: string) { - return this.config.javaConfig[serverid].executable; - }; - - /** - * Set the path of the Java Executable. - * - * @param {string} serverid The server id. - * @param {string} executable The new path of the Java Executable. - */ - public static setJavaExecutable(serverid: string, executable: string) { - this.config.javaConfig[serverid].executable = executable; - }; - - /** - * Retrieve the additional arguments for JVM initialization. Required arguments, - * such as memory allocation, will be dynamically resolved and will not be included - * in this value. - * - * @param {string} serverid The server id. - * @returns {Array.} An array of the additional arguments for JVM initialization. - */ - public static getJVMOptions(serverid: string) { - return this.config.javaConfig[serverid].jvmOptions; - }; - - /** - * Set the additional arguments for JVM initialization. Required arguments, - * such as memory allocation, will be dynamically resolved and should not be - * included in this value. - * - * @param {string} serverid The server id. - * @param {Array.} jvmOptions An array of the new additional arguments for JVM - * initialization. - */ - public static setJVMOptions(serverid: string, jvmOptions: string[]) { - this.config.javaConfig[serverid].jvmOptions = jvmOptions; - }; - - // Game Settings - - /** - * Retrieve the width of the game window. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {number} The width of the game window. - */ - public static getGameWidth(def = false) { - return !def ? this.config.settings.game.resWidth : this.DEFAULT_CONFIG.settings.game.resWidth; - }; - - /** - * Set the width of the game window. - * - * @param {number} resWidth The new width of the game window. - */ - public static setGameWidth(resWidth: number) { - if (typeof resWidth !== "number") throw new Error("Only Accept Number") - this.config.settings.game.resWidth = resWidth; - }; - - /** - * Validate a potential new width value. - * - * @param {number} resWidth The width value to validate. - * @returns {boolean} Whether or not the value is valid. - */ - public static validateGameWidth(resWidth: number) { - if (typeof resWidth !== "number") throw new Error("Only Accept Number") - return Number.isInteger(resWidth) && resWidth >= 0; - }; - - /** - * Retrieve the height of the game window. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {number} The height of the game window. - */ - public static getGameHeight(def = false) { - return !def ? this.config.settings.game.resHeight : this.DEFAULT_CONFIG.settings.game.resHeight; - }; - - /** - * Set the height of the game window. - * - * @param {number} resHeight The new height of the game window. - */ - public static setGameHeight(resHeight: number) { - if (typeof resHeight !== "number") throw new Error("Only Accept Number") - this.config.settings.game.resHeight = resHeight; - }; - - /** - * Validate a potential new height value. - * - * @param {number} resHeight The height value to validate. - * @returns {boolean} Whether or not the value is valid. - */ - public static validateGameHeight(resHeight: number) { - if (typeof resHeight !== "number") throw new Error("Only Accept Number") - return Number.isInteger(resHeight) && resHeight >= 0; - }; - - /** - * Check if the game should be launched in fullscreen mode. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the game is set to launch in fullscreen mode. - */ - public static getFullscreen(def = false) { - return !def ? this.config.settings.game.fullscreen : this.DEFAULT_CONFIG.settings.game.fullscreen; - }; - - /** - * Change the status of if the game should be launched in fullscreen mode. - * - * @param {boolean} fullscreen Whether or not the game should launch in fullscreen mode. - */ - public static setFullscreen(fullscreen: boolean) { - this.config.settings.game.fullscreen = fullscreen; - }; - - /** - * Check if the game should auto connect to servers. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the game should auto connect to servers. - */ - public static getAutoConnect(def = false) { - return !def ? this.config.settings.game.autoConnect : this.DEFAULT_CONFIG.settings.game.autoConnect; - }; - - /** - * Change the status of whether or not the game should auto connect to servers. - * - * @param {boolean} autoConnect Whether or not the game should auto connect to servers. - */ - public static setAutoConnect(autoConnect: boolean) { - this.config.settings.game.autoConnect = autoConnect; - }; - - /** - * Check if the game should launch as a detached process. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the game will launch as a detached process. - */ - public static getLaunchDetached(def = false) { - return !def ? this.config.settings.game.launchDetached : this.DEFAULT_CONFIG.settings.game.launchDetached; - }; - - /** - * Change the status of whether or not the game should launch as a detached process. - * - * @param {boolean} launchDetached Whether or not the game should launch as a detached process. - */ - public static setLaunchDetached(launchDetached: boolean) { - this.config.settings.game.launchDetached = launchDetached; - }; - - // Launcher Settings - - /** - * Check if the launcher should download prerelease versions. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {boolean} Whether or not the launcher should download prerelease versions. - */ - public static getAllowPrerelease(def = false) { - return !def ? this.config.settings.launcher.allowPrerelease : this.DEFAULT_CONFIG.settings.launcher.allowPrerelease; - }; - - /** - * Change the status of Whether or not the launcher should download prerelease versions. - * - * @param {boolean} launchDetached Whether or not the launcher should download prerelease versions. - */ - public static setAllowPrerelease(allowPrerelease: boolean) { - this.config.settings.launcher.allowPrerelease = allowPrerelease; - }; - - private static defaultJavaConfig(mcVersion: string) { - if (MinecraftUtil.mcVersionAtLeast("1.17", mcVersion)) { - return this.defaultJavaConfig117(); - } else { - return this.defaultJavaConfigBelow117(); - } - } - - private static defaultJavaConfigBelow117() { - return { - minRAM: resolveMinRAM(), - maxRAM: resolveMaxRAM(), // Dynamic - executable: null, - jvmOptions: ["-XX:+UseConcMarkSweepGC", "-XX:+CMSIncrementalMode", "-XX:-UseAdaptiveSizePolicy", "-Xmn128M"], - }; - } - - private static defaultJavaConfig117() { - return { - minRAM: resolveMinRAM(), - maxRAM: resolveMaxRAM(), // Dynamic - executable: null, - jvmOptions: ["-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", "-XX:G1NewSizePercent=20", "-XX:G1ReservePercent=20", "-XX:MaxGCPauseMillis=50", "-XX:G1HeapRegionSize=32M"], - }; - } - - /** - * Validate that the destination object has at least every field - * present in the source object. Assign a default value otherwise. - * - * @param {Object} srcObj The source object to reference against. - * @param {Object} destObj The destination object. - * @returns {Object} A validated destination object. - */ - private static validateKeySet(srcObj, destObj) { - if (srcObj == null) { - srcObj = {}; - } - const validationBlacklist = ["authenticationDatabase", "javaConfig"]; - const keys = Object.keys(srcObj); - for (let i = 0; i < keys.length; i++) { - if (typeof destObj[keys[i]] === "undefined") { - destObj[keys[i]] = srcObj[keys[i]]; - } else if (typeof srcObj[keys[i]] === "object" && srcObj[keys[i]] != null && !(srcObj[keys[i]] instanceof Array) && validationBlacklist.indexOf(keys[i]) === -1) { - destObj[keys[i]] = this.validateKeySet(srcObj[keys[i]], destObj[keys[i]]); - } - } - return destObj; - } - - -} - diff --git a/src/manager/DistroManager.ts b/src/manager/DistroManager.ts deleted file mode 100644 index baaed737..00000000 --- a/src/manager/DistroManager.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { readFile, writeFile } from "fs-extra" -import { LoggerUtil } from "helios-core/."; -import { DevUtil } from '../util/DevUtil'; -import { ConfigManager } from "./ConfigManager"; -import { join } from 'path'; -import { DistroIndex, IDistroIndex } from '../models/DistroIndex'; -import fetch from 'node-fetch'; - -const logger = LoggerUtil.getLogger('DistroManager') -export enum DistroTypes { - Library, - ForgeHosted, - Forge, // Unimplemented - LiteLoader, - ForgeMod, - LiteMod, - File, - VersionManifest, -} - -export class DistroManager { - - public static distribution?: DistroIndex; - private static readonly DISTRO_PATH = join(ConfigManager.getLauncherDirectory(), 'distribution.json') - private static readonly DEV_PATH = join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json') - - /** - * @returns {Promise.} - */ - public static async pullRemote() { - if (DevUtil.IsDev) return this.pullLocal(); - - const distroDest = join(ConfigManager.getLauncherDirectory(), 'distribution.json') - const response = await fetch(ConfigManager.DistributionURL, { signal: AbortSignal.timeout(2500) }); - - this.distribution = DistroIndex.fromJSON(await response.json() as IDistroIndex); - - writeFile(distroDest, JSON.stringify(this.distribution), 'utf-8').catch(e => { - logger.warn("Failed to save local distribution.json") - logger.warn(e); - }); - - return this.distribution; - } - - /** - * @returns {Promise.} - */ - public static async pullLocal() { - const file = await readFile(DevUtil.IsDev ? this.DEV_PATH : this.DISTRO_PATH, 'utf-8'); - this.distribution = DistroIndex.fromJSON(JSON.parse(file)); - return this.distribution; - - } - - public static setDevMode(value: boolean) { - if (value) { - logger.info('Developer mode enabled.') - logger.info('If you don\'t know what that means, revert immediately.') - } else { - logger.info('Developer mode disabled.') - } - DevUtil.IsDev = value - } -} \ No newline at end of file diff --git a/src/models/Artifact.ts b/src/models/Artifact.ts deleted file mode 100644 index abb6b976..00000000 --- a/src/models/Artifact.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Represents the download information - * for a specific module. - */ - -export interface IArtifact { - MD5: string, - size: string, - url: string, - path: string, -} - -export class Artifact { - - /** - * Parse a JSON object into an Artifact. - * - * @param {Object} json A JSON object representing an Artifact - * - * @returns {Artifact} The parsed Artifact. - */ - public static fromJSON(json: IArtifact) { - return new Artifact(json.MD5, json.size, json.url, json.path) - } - - constructor( - public MD5: string, - public size: string, - public url: string, - public path: string, - ) { } - - //TODO: Remove those property - - /** - * Get the MD5 hash of the artifact. This value may - * be undefined for artifacts which are not to be - * validated and updated. - * - * @returns {string} The MD5 hash of the Artifact or undefined. - */ - public getHash() { - return this.MD5 - } - - /** - * @returns {number} The download size of the artifact. - */ - public getSize() { - return this.size - } - - /** - * @returns {string} The download url of the artifact. - */ - public getURL() { - return this.url - } - - /** - * @returns {string} The artifact's destination path. - */ - public getPath() { - return this.path - } - -} \ No newline at end of file diff --git a/src/models/Asset.ts b/src/models/Asset.ts deleted file mode 100644 index 761d300b..00000000 --- a/src/models/Asset.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** Class representing a base asset. */ - -export interface IAsset { - id: string, - hash: string, - size: number, - from: string, - to: string -} - -export class Asset { - /** - * Create an asset. - * - * @param {string} id The id of the asset. - * @param {string} hash The hash value of the asset. - * @param {number} size The size in bytes of the asset. - * @param {string} from The url where the asset can be found. - * @param {string} to The absolute local file path of the asset. - */ - constructor( - public id: string, - public hash: string, - public size: number, - public from: string, - public to: string - ) { - } -} \ No newline at end of file diff --git a/src/models/DLTracker.ts b/src/models/DLTracker.ts deleted file mode 100644 index 5e51fbd9..00000000 --- a/src/models/DLTracker.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Asset } from './Asset'; -/** - * Class representing a download tracker. This is used to store meta data - * about a download queue, including the queue itself. - */ -export class DLTracker { - - /** - * Create a DLTracker - * - * @param {Array.} dlqueue An array containing assets queued for download. - * @param {number} dlsize The combined size of each asset in the download queue array. - * @param {function(Asset)} callback Optional callback which is called when an asset finishes downloading. - */ - constructor( - public dlqueue: Asset[], - public dlsize: number, - public callback?: (asset: Asset) => void) { - } - -} \ No newline at end of file diff --git a/src/models/DistroAsset.ts b/src/models/DistroAsset.ts deleted file mode 100644 index 9d215197..00000000 --- a/src/models/DistroAsset.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Asset } from "./Asset" -import { DistroTypes } from '../manager/DistroManager'; - -export class DistroAsset extends Asset { - - /** - * Create a DistroModule. This is for processing, - * not equivalent to the module objects in the - * distro index. - * - * @param {any} id The id of the asset. - * @param {string} hash The hash value of the asset. - * @param {number} size The size in bytes of the asset. - * @param {string} from The url where the asset can be found. - * @param {string} to The absolute local file path of the asset. - * @param {string} type The the module type. - */ - constructor(public id: any, - hash: string, - size: number, - from: string, - to: string, - public type: DistroTypes - ) { - super(id, hash, size, from, to) - this.type = type - } - -} \ No newline at end of file diff --git a/src/models/DistroIndex.ts b/src/models/DistroIndex.ts deleted file mode 100644 index 541d8c31..00000000 --- a/src/models/DistroIndex.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { IServer, Server } from './Server'; - -export interface IDistroIndex { - version: string, - rss: string, - servers: IServer[] -} - -export class DistroIndex { - - /** - * Parse a JSON object into a DistroIndex. - * - * @param {Object} json A JSON object representing a DistroIndex. - * - * @returns {DistroIndex} The parsed Server object. - */ - public static fromJSON(json: IDistroIndex) { - return new DistroIndex( - json.version, - json.rss, - json.servers - ) - } - - public servers: Server[] = [] - public get mainServer() { - return this.servers.find(x => x.isMainServer)?.id ?? this.servers[0].id ?? null; - } - - constructor( - public version: string, - public rss: string, - servers: IServer[], - ) { - this.resolveServers(servers); - } - - public getServer(id: string) { - return this.servers.find(server => server.id === id); - } - - private resolveServers(serverJsons: IServer[]) { - const servers: Server[] = [] - for (let serverJson of serverJsons) { - servers.push(Server.fromJSON(serverJson)) - } - this.servers = servers - } - -} \ No newline at end of file diff --git a/src/models/Library.ts b/src/models/Library.ts deleted file mode 100644 index 02641da8..00000000 --- a/src/models/Library.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Asset } from './Asset'; -export class Library extends Asset { - - /** - * Converts the process.platform OS names to match mojang's OS names. - */ - public static mojangFriendlyOS() { - switch (process.platform) { - case "darwin": - return 'osx'; - case "linux": - return 'linux'; - case "win32": - return 'windows'; - default: - return 'unknown_os' - } - } - - /** - * Checks whether or not a library is valid for download on a particular OS, following - * the rule format specified in the mojang version data index. If the allow property has - * an OS specified, then the library can ONLY be downloaded on that OS. If the disallow - * property has instead specified an OS, the library can be downloaded on any OS EXCLUDING - * the one specified. - * - * If the rules are undefined, the natives property will be checked for a matching entry - * for the current OS. - * - * @param {Array.} rules The Library's download rules. - * @param {Object} natives The Library's natives object. - * @returns {boolean} True if the Library follows the specified rules, otherwise false. - */ - public static validateRules(rules, natives) { - if (rules == null) { - return natives ? natives[Library.mojangFriendlyOS()] != null : true; - } - - for (let rule of rules) { - const action = rule.action - const osProp = rule.os - if (action != null && osProp != null) { - const osName = osProp.name - const osMoj = Library.mojangFriendlyOS() - if (action === 'allow') { - return osName === osMoj - } else if (action === 'disallow') { - return osName !== osMoj - } - } - } - return true - } -} \ No newline at end of file diff --git a/src/models/Module.ts b/src/models/Module.ts deleted file mode 100644 index f7ed7c6f..00000000 --- a/src/models/Module.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { LoggerUtil } from 'helios-core/.'; -import { ConfigManager } from '../manager/ConfigManager'; -import { Artifact, IArtifact } from './Artifact'; -import { join } from 'path'; -import { DistroTypes } from '../manager/DistroManager'; -import { Required, IRequired } from './Required'; - -const logger = LoggerUtil.getLogger('Module') - -export interface IModule { - artifactExt: string; - artifactClassifier?: string; - artifactVersion: string; - artifactID: string; - artifactGroup: string; - subModules: IModule[]; - required: IRequired; - artifact: IArtifact; - id: string, - name: string, - type: DistroTypes, - classpath: boolean -} - -export class Module { - - /** - * Parse a JSON object into a Module. - * - * @param {Object} json A JSON object representing a Module. - * @param {string} serverid The ID of the server to which this module belongs. - * - * @returns {Module} The parsed Module. - */ - public static fromJSON(json: IModule, serverid: string) { - return new Module(json.id, json.name, json.type, json.classpath, json.required, json.artifact, json.subModules, serverid) - } - - /** - * Resolve the default extension for a specific module type. - * - * @param {string} type The type of the module. - * - * @return {string} The default extension for the given type. - */ - private static resolveDefaultExtension(type) { - switch (type) { - case exports.Types.Library: - case exports.Types.ForgeHosted: - case exports.Types.LiteLoader: - case exports.Types.ForgeMod: - return 'jar' - case exports.Types.LiteMod: - return 'litemod' - case exports.Types.File: - default: - return 'jar' // There is no default extension really. - } - } - - - public artifactExt: string; - public artifactClassifier?: string; - public artifactVersion: string; - public artifactID: string; - public artifactGroup: string; - - public subModules: Module[] = [] - public required: Required; - public artifact: Artifact - /** - * @returns {string} The identifier without he version or extension. - */ - public get versionlessID() { - return this.artifactGroup + ':' + this.artifactID - } - - /** - * @returns {string} The identifier without the extension. - */ - public get extensionlessID() { - return this.identifier.split('@')[0] - } - - public get hasSubModules() { - return this.subModules.length > 0; - } - - - - constructor( - public identifier: string, - public name: string, - public type: DistroTypes, - public classpath: boolean = true, - required: IRequired, - artifact: IArtifact, - subModules: IModule[], - serverid: string - ) { - this.required = Required.fromJSON(required); - this.artifact = Artifact.fromJSON(artifact); - - this.resolveMetaData() - this.resolveArtifactPath(this.artifact.path, serverid) - this.resolveSubModules(subModules, serverid) - } - - private resolveMetaData() { - try { - - const m0 = this.identifier.split('@') - - this.artifactExt = m0[1] || Module.resolveDefaultExtension(this.type) - - const m1 = m0[0].split(':') - - this.artifactClassifier = m1[3] || undefined - this.artifactVersion = m1[2] || '???' - this.artifactID = m1[1] || '???' - this.artifactGroup = m1[0] || '???' - - } catch (err) { - // Improper identifier - logger.error('Improper ID for module', this.identifier, err) - } - } - - private resolveArtifactPath(artifactPath: string, serverid) { - const pth = artifactPath == null ? join(...this.artifactGroup.split('.'), this.artifactID, this.artifactVersion, `${this.artifactID}-${this.artifactVersion}${this.artifactClassifier != undefined ? `-${this.artifactClassifier}` : ''}.${this.artifactExt}`) : artifactPath - - switch (this.type) { - case exports.Types.Library: - case exports.Types.ForgeHosted: - case exports.Types.LiteLoader: - this.artifact.path = join(ConfigManager.commonDirectory, 'libraries', pth) - break - case exports.Types.ForgeMod: - case exports.Types.LiteMod: - this.artifact.path = join(ConfigManager.commonDirectory, 'modstore', pth) - break - case exports.Types.VersionManifest: - this.artifact.path = join(ConfigManager.commonDirectory, 'versions', this.identifier, `${this.identifier}.json`) - break - case exports.Types.File: - default: - this.artifact.path = join(ConfigManager.instanceDirectory, serverid, pth) - break - } - - } - - private resolveSubModules(json, serverid) { - if (json == null) return; - - const subModules: Module[] = [] - for (let sm of json) { - subModules.push(Module.fromJSON(sm, serverid)) - } - this.subModules = subModules - } - -} \ No newline at end of file diff --git a/src/models/Required.ts b/src/models/Required.ts deleted file mode 100644 index 97919691..00000000 --- a/src/models/Required.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Represents a the requirement status - * of a module. - */ - -export interface IRequired { - value: any, - def: any, -} - -export class Required { - - /** - * Parse a JSON object into a Required object. - * - * @param {Object} json A JSON object representing a Required object. - * - * @returns {Required} The parsed Required object. - */ - public static fromJSON(json: IRequired) { - if (json == null) { - return new Required(true, true) - } else { - return new Required(json.value == null ? true : json.value, json.def == null ? true : json.def) - } - } - - public default: any; - constructor( - public value: any, - def: any - ) { - this.default = def; - } - - /** - * Get the default value for a required object. If a module - * is not required, this value determines whether or not - * it is enabled by default. - * - * @returns {boolean} The default enabled value. - */ - public get isDefault() { - return this.default - } - - /** - * @returns {boolean} Whether or not the module is required. - */ - public get isRequired(): boolean { - return this.value - } - -} \ No newline at end of file diff --git a/src/models/Server.ts b/src/models/Server.ts deleted file mode 100644 index ee1b948c..00000000 --- a/src/models/Server.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Module } from "./Module" - -export interface IServer { - id: string, - name: string, - description: string, - icon: string, - version: string, - address: string, - minecraftVersion: string, - isMainServer: boolean, - autoconnect: boolean, - modules: Module[], -} - -/** - * Represents a server configuration. - */ -export class Server { - /** - * Parse a JSON object into a Server. - * - * @param {Object} json A JSON object representing a Server. - * - * @returns {Server} The parsed Server object. - */ - public static fromJSON(json: IServer) { - - const mdls = json.modules - json.modules = [] - - const serv = new Server( - json.id, - json.name, - json.description, - json.icon, - json.version, - json.address, - json.minecraftVersion, - json.isMainServer, - json.autoconnect, - json.modules - ) - serv.resolveModules(mdls) - - return serv - } - - - constructor( - public id: string, - public name: string, - public description: string, - public icon: string, - public version: string, - public address: string, - public minecraftVersion: string, - public isMainServer: boolean, - public autoconnect: boolean, - public modules: Module[] = [], - ) { } - - - private resolveModules(json) { - const modules: Module[] = [] - for (let m of json) { - modules.push(Module.fromJSON(m, this.id)) - } - this.modules = modules - } - - -} \ No newline at end of file diff --git a/src/scripts/LangLoader.ts b/src/scripts/LangLoader.ts deleted file mode 100644 index c4362dc9..00000000 --- a/src/scripts/LangLoader.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readFileSync } from 'fs-extra'; -import { join } from 'path'; -import type LangType from "../lang/en_US.json"; - -export class LangLoader { - public static lang?: typeof LangType; - - public static loadLanguage(langId: string) { - this.lang = readFileSync(join(__dirname, '..', 'lang', `${langId}.json`)).toJSON() as any || undefined; - } - - public static query(langId: string) { - if (!this.lang) return ""; - - let query = langId.split('.') - let res = this.lang - for (let q of query) { - res = res[q] - } - return res === this.lang ? {} : res - } - - public static queryJS(id) { - return this.query(`js.${id}`) - } -} \ No newline at end of file diff --git a/src/scripts/Preloading.ts b/src/scripts/Preloading.ts deleted file mode 100644 index 06fd157a..00000000 --- a/src/scripts/Preloading.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ipcRenderer } from "electron"; -import { remove } from "fs-extra"; -import { LoggerUtil } from "helios-core/."; -import { ConfigManager } from "../manager/ConfigManager"; -import { DistroManager } from "../manager/DistroManager"; -import { join } from 'path'; -import os from 'os'; -import { LangLoader } from "./LangLoader"; - -const logger = LoggerUtil.getLogger('Preloader') -logger.info('Loading..') - - -// Load ConfigManager -ConfigManager.load() - -// Load Strings -LangLoader.loadLanguage('en_US') - -function onDistroLoad(data) { - if (data != null) { - - // Resolve the selected server if its value has yet to be set. - if (ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null) { - logger.info('Determining default selected server..') - ConfigManager.selectedServer = data.getMainServer().getID(); - ConfigManager.save() - } - } - ipcRenderer.send('distributionIndexDone', data != null) -} - -// Ensure Distribution is downloaded and cached. -DistroManager.pullRemote().then((data) => { - logger.info('Loaded distribution index.') - - onDistroLoad(data) - -}).catch((err) => { - logger.info('Failed to load distribution index.') - logger.error(err) - - logger.info('Attempting to load an older version of the distribution index.') - // Try getting a local copy, better than nothing. - DistroManager.pullLocal().then((data) => { - logger.info('Successfully loaded an older version of the distribution index.') - - onDistroLoad(data) - - - }).catch((err) => { - - logger.info('Failed to load an older version of the distribution index.') - logger.info('Application cannot run.') - logger.error(err) - - onDistroLoad(null) - - }) - -}) - -// Clean up temp dir incase previous launches ended unexpectedly. -remove(join(os.tmpdir(), ConfigManager.tempNativeFolder), (err) => { - if (err) { - logger.warn('Error while cleaning natives directory', err) - } else { - logger.info('Cleaned natives directory.') - } -}) \ No newline at end of file diff --git a/src/scripts/views/welcome.ts b/src/scripts/views/welcome.ts deleted file mode 100644 index d2bd7cc5..00000000 --- a/src/scripts/views/welcome.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Script for welcome.ejs - */ -document.getElementById('welcomeButton')?.addEventListener('click', e => { - loginOptionsCancelEnabled(false) // False by default, be explicit. - loginOptionsViewOnLoginSuccess = VIEWS.landing - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - switchView(VIEWS.welcome, VIEWS.loginOptions) -}) \ No newline at end of file diff --git a/src/services/AssetGuard.ts b/src/services/AssetGuard.ts deleted file mode 100644 index 91d089d0..00000000 --- a/src/services/AssetGuard.ts +++ /dev/null @@ -1,873 +0,0 @@ -import { LoggerUtil } from "helios-core/."; -import * as EventEmitter from 'events'; -import { DLTracker } from '../models/DLTracker'; -import { createHash } from "crypto"; -import { existsSync, readFileSync, readFile, ensureDirSync, writeFileSync, createWriteStream, unlink, createReadStream, remove, ensureDir, writeFile, pathExists, pathExistsSync } from 'fs-extra'; -import AdmZip from 'adm-zip'; -import { DevUtil } from '../util/DevUtil'; -import { join } from 'path'; -import { spawn } from 'child_process'; -import request from 'request'; -import asyncModule from "async"; -import { Asset } from "../models/Asset"; -import { Library } from '../models/Library'; -import { DistroAsset } from '../models/DistroAsset'; -import { Module } from '../models/Module'; -import { Artifact } from '../models/Artifact'; -import { Server } from '../models/Server'; -import { DistroManager, DistroTypes } from '../manager/DistroManager'; -import { MinecraftUtil } from "../util/MinecraftUtil"; -import { createGunzip } from "zlib"; -import { extract } from "tar-fs"; -import { JavaGuard } from './JavaGuard'; -import { StreamZipAsync } from "node-stream-zip"; -import { ConfigManager } from "../manager/ConfigManager"; -import fetch from 'node-fetch'; -import { MinecraftGameManifest, MinecraftGameVersionManifest, MinecraftAssetJson } from '../dto/Minecraft'; -const logger = LoggerUtil.getLogger('AssetGuard'); - -export class AssetGuard extends EventEmitter { - - - // Static Utility Functions - // #region - - // Static Hash Validation Functions - // #region - - /** - * Calculates the hash for a file using the specified algorithm. - * - * @param {Buffer} buffer The buffer containing file data. - * @param {string} algo The hash algorithm. - * @returns {string} The calculated hash in hex. - */ - private static calculateHash(buffer: Buffer, algo: string): string { - return createHash(algo).update(buffer).digest('hex') - } - - /** - * Used to parse a checksums file. This is specifically designed for - * the checksums.sha1 files found inside the forge scala dependencies. - * - * @param {string} content The string content of the checksums file. - * @returns { Record} An object with keys being the file names, and values being the hashes. - */ - private static parseChecksumsFile(content: string): Record { - let finalContent: Record = {} - let lines = content.split('\n') - for (let i = 0; i < lines.length; i++) { - let bits = lines[i].split(' ') - if (bits[1] == null) { - continue - } - finalContent[bits[1]] = bits[0] - } - return finalContent - } - - /** - * Validate that a file exists and matches a given hash value. - * - * @param {string} filePath The path of the file to validate. - * @param {string} algo The hash algorithm to check against. - * @param {string} hash The existing hash to check against. - * @returns {boolean} True if the file exists and calculated hash matches the given hash, otherwise false. - */ - private static validateLocal(filePath: string, algo: string, hash: string): boolean { - if (existsSync(filePath)) { - //No hash provided, have to assume it's good. - if (hash == null) { - return true - } - let buf = readFileSync(filePath) - let calcdhash = AssetGuard.calculateHash(buf, algo) - return calcdhash === hash.toLowerCase() - } - return false - } - - - /** - * Validates a file in the style used by forge's version index. - * - * @param {string} filePath The path of the file to validate. - * @param {Array.} checksums The checksums listed in the forge version index. - * @returns {boolean} True if the file exists and the hashes match, otherwise false. - */ - private static validateForgeChecksum(filePath: string, checksums: string[]): boolean { - if (existsSync(filePath)) { - if (checksums == null || checksums.length === 0) { - return true - } - let buf = readFileSync(filePath) - let calcdhash = AssetGuard.calculateHash(buf, 'sha1') - let valid = checksums.includes(calcdhash) - if (!valid && filePath.endsWith('.jar')) { - valid = AssetGuard.validateForgeJar(Buffer.from(filePath), checksums) - } - return valid - } - return false - } - - /** - * Validates a forge jar file dependency who declares a checksums.sha1 file. - * This can be an expensive task as it usually requires that we calculate thousands - * of hashes. - * - * @param {Buffer} buffer The buffer of the jar file. - * @param {Array.} checksums The checksums listed in the forge version index. - * @returns {boolean} True if all hashes declared in the checksums.sha1 file match the actual hashes. - */ - private static validateForgeJar(buffer: Buffer, checksums: string[]): boolean { - // Double pass method was the quickest I found. I tried a version where we store data - // to only require a single pass, plus some quick cleanup but that seemed to take slightly more time. - - const hashes: Record = {} - let expected: Record = {} - - const zip = new AdmZip(buffer) - const zipEntries = zip.getEntries() - - //First pass - for (let i = 0; i < zipEntries.length; i++) { - let entry = zipEntries[i] - if (entry.entryName === 'checksums.sha1') { - expected = AssetGuard.parseChecksumsFile(zip.readAsText(entry)) - } - hashes[entry.entryName] = AssetGuard.calculateHash(entry.getData(), 'sha1') - } - - if (!checksums.includes(hashes['checksums.sha1'])) { - return false - } - - //Check against expected - const expectedEntries = Object.keys(expected) - for (let i = 0; i < expectedEntries.length; i++) { - if (expected[expectedEntries[i]] !== hashes[expectedEntries[i]]) { - return false - } - } - return true - } - - // #endregion - - // Miscellaneous Static Functions - // #region - - /** - * Extracts and unpacks a file from .pack.xz format. - * - * @param {Array.} filePaths The paths of the files to be extracted and unpacked. - * @returns {Promise.} An empty promise to indicate the extraction has completed. - */ - private static extractPackXZ(filePaths: string[], javaExecutable: string): Promise { - const extractLogger = LoggerUtil.getLogger('PackXZExtract') - extractLogger.info('Starting') - return new Promise((resolve, reject) => { - let libPath: string; - if (DevUtil.IsDev) { - libPath = join(process.cwd(), 'libraries', 'java', 'PackXZExtract.jar') - } else { - if (process.platform === 'darwin') { - libPath = join(process.cwd(), 'Contents', 'Resources', 'libraries', 'java', 'PackXZExtract.jar') - } else { - libPath = join(process.cwd(), 'resources', 'libraries', 'java', 'PackXZExtract.jar') - } - } - - const filePath = filePaths.join(',') - const child = spawn(javaExecutable, ['-jar', libPath, '-packxz', filePath]) - child.stdout.on('data', (data) => { - extractLogger.info(data.toString('utf8')) - }) - child.stderr.on('data', (data) => { - extractLogger.info(data.toString('utf8')) - }) - child.on('close', (code, _signal) => { - extractLogger.info('Exited with code', code) - resolve(undefined); - }) - }) - } - - - /** - * Function which finalizes the forge installation process. This creates a 'version' - * instance for forge and saves its version.json file into that instance. If that - * instance already exists, the contents of the version.json file are read and returned - * in a promise. - * - * @param {Asset} asset The Asset object representing Forge. - * @param {string} commonPath The common path for shared game files. - * @returns {Promise.} A promise which resolves to the contents of forge's version.json. - */ - private static finalizeForgeAsset(asset: Asset, commonPath: string): Promise { - return new Promise((resolve, reject) => { - readFile(asset.to, (err, data) => { - const zip = new AdmZip(data) - const zipEntries = zip.getEntries() - - for (let i = 0; i < zipEntries.length; i++) { - if (zipEntries[i].entryName === 'version.json') { - const forgeVersion = JSON.parse(zip.readAsText(zipEntries[i])) - const versionPath = join(commonPath, 'versions', forgeVersion.id) - const versionFile = join(versionPath, forgeVersion.id + '.json') - if (!existsSync(versionFile)) { - ensureDirSync(versionPath) - writeFileSync(join(versionPath, forgeVersion.id + '.json'), zipEntries[i].getData()) - resolve(forgeVersion) - } else { - //Read the saved file to allow for user modifications. - resolve(JSON.parse(readFileSync(versionFile, 'utf-8'))) - } - return - } - } - //We didn't find forge's version.json. - reject('Unable to finalize Forge processing, version.json not found! Has forge changed their format?') - }) - }) - } - - // #endregion - - // #endregion - - - - - - public totaldlsize = 0; - public progress = 0; - public assets = new DLTracker([], 0); - public libraries = new DLTracker([], 0); - public files = new DLTracker([], 0); - public forge = new DLTracker([], 0); - public java = new DLTracker([], 0); - public extractQueue: string[] = []; - - /** - * Create an instance of AssetGuard. - * On creation the object's properties are never-null default - * values. Each identifier is resolved to an empty DLTracker. - * - * @param {string} commonPath The common path for shared game files. - * @param {string} javaexec The path to a java executable which will be used - * to finalize installation. - */ - constructor( - public commonPath: string, - public javaexec: string - ) { - super() - this.commonPath = commonPath - this.javaexec = javaexec - } - - // Validation Functions - // #region - - /** - * Loads the version data for a given minecraft version. - * - * @param {string} version The game version for which to load the index data. - * @param {boolean} force Optional. If true, the version index will be downloaded even if it exists locally. Defaults to false. - * @returns {Promise.} Promise which resolves to the version data object. - */ - public async loadVersionData(version: string, force: boolean = false): Promise { - const versionPath = join(this.commonPath, 'versions', version) - const versionFile = join(versionPath, version + '.json') - - if (!existsSync(versionFile) || force) { - const url = await this.getVersionDataUrl(version) - if (!url) throw new Error("No URL"); - - //This download will never be tracked as it's essential and trivial. - logger.info('Preparing download of ' + version + ' assets.') - await ensureDir(versionPath); - - const response = await fetch(url); - const json = await response.json() as MinecraftGameVersionManifest; - response.text().then(text => { - writeFile(versionFile, response.text()); - }); - return json; - } - - return JSON.parse(await readFile(versionFile, 'utf-8')); - } - - /** - * Parses Mojang's version manifest and retrieves the url of the version - * data index. - * - * //TODO:Get the JSON to type - * - * @param {string} version The version to lookup. - * @returns {Promise.} Promise which resolves to the url of the version data index. - * If the version could not be found, resolves to null. - */ - public async getVersionDataUrl(versionId: string): Promise { - - const response = await fetch('https://launchermeta.mojang.com/mc/game/version_manifest.json'); - const manifest = await response.json() as MinecraftGameManifest; - const version = manifest.versions.find(v => v.id === versionId) - return version?.url || null - } - - // Asset (Category=''') Validation Functions - // #region - - /** - * Public asset validation function. This function will handle the validation of assets. - * It will parse the asset index specified in the version data, analyzing each - * asset entry. In this analysis it will check to see if the local file exists and is valid. - * If not, it will be added to the download queue for the 'assets' identifier. - * - * @param {MinecraftGameVersionManifest} versionData The version data for the assets. - * @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - public async validateAssets(versionData: MinecraftGameVersionManifest, force: boolean = false): Promise { - return this.assetChainIndexData(versionData, force); - } - - //Chain the asset tasks to provide full async. The below functions are private. - /** - * Private function used to chain the asset validation process. This function retrieves - * the index data. - * @param {MinecraftGameVersionManifest} versionData - * @param {boolean} force - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - private async assetChainIndexData(versionData: MinecraftGameVersionManifest, force: boolean = false): Promise { - //Asset index constants. - const assetIndex = versionData.assetIndex - const name = assetIndex.id + '.json' - const indexPath = join(this.commonPath, 'assets', 'indexes') - const assetIndexLoc = join(indexPath, name) - - let assetJson: MinecraftAssetJson; - if (force || !pathExistsSync(assetIndexLoc)) { - logger.info('Downloading ' + versionData.id + ' asset index.') - await ensureDir(indexPath) - - const response = await fetch(assetIndex.url); - assetJson = await response.json() as MinecraftAssetJson; - response.text().then(txt => { - writeFile(assetIndexLoc, txt, { encoding: 'utf8' }) - }); - } else { - assetJson = JSON.parse(await readFile(assetIndexLoc, 'utf-8')) as MinecraftAssetJson; - } - - return this.assetChainValidateAssets(assetJson) - } - - /** - * Private function used to chain the asset validation process. This function processes - * the assets and enqueues missing or invalid files. - * @param {MinecraftGameVersionManifest} versionData - * @param {boolean} force - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - private assetChainValidateAssets(indexData: MinecraftAssetJson): Promise { - return new Promise((resolve, reject) => { - - //Asset constants - const resourceURL = 'https://resources.download.minecraft.net/' - const localPath = join(this.commonPath, 'assets') - const objectPath = join(localPath, 'objects') - - const assetDlQueue: Asset[] = [] - let dlSize = 0 - let acc = 0 - const total = Object.keys(indexData.objects).length - //const objKeys = Object.keys(data.objects) - asyncModule.forEachOfLimit(indexData.objects, 10, (value, key, cb) => { - acc++ - this.emit('progress', 'assets', acc, total) - const hash = value.hash - const assetName = join(hash.substring(0, 2), hash) - const urlName = hash.substring(0, 2) + '/' + hash - const ast = new Asset(key, hash, value.size, resourceURL + urlName, join(objectPath, assetName)) - if (!AssetGuard.validateLocal(ast.to, 'sha1', ast.hash)) { - dlSize += (ast.size * 1) - assetDlQueue.push(ast) - } - cb() - }, (err) => { - this.assets = new DLTracker(assetDlQueue, dlSize) - resolve(undefined) - }) - }) - } - - // #endregion - // Library (Category=''') Validation Functions - // #region - - /** - * Public library validation function. This function will handle the validation of libraries. - * It will parse the version data, analyzing each library entry. In this analysis, it will - * check to see if the local file exists and is valid. If not, it will be added to the download - * queue for the 'libraries' identifier. - * - * @param {MinecraftGameVersionManifest} versionData The version data for the assets. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - public validateLibraries(versionData: MinecraftGameVersionManifest): Promise { - return new Promise((resolve, reject) => { - - const libArr = versionData.libraries - const libPath = join(this.commonPath, 'libraries') - - const libDlQueue: Library[] = [] - let dlSize = 0 - - //Check validity of each library. If the hashs don't match, download the library. - asyncModule.eachLimit(libArr, 5, (lib, cb) => { - if (Library.validateRules(lib.rules, lib.natives)) { - let artifact = (lib.natives == null) ? lib.downloads.artifact : lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))] - const libItm = new Library(lib.name, artifact.sha1, artifact.size, artifact.url, join(libPath, artifact.path)) - if (!AssetGuard.validateLocal(libItm.to, 'sha1', libItm.hash)) { - dlSize += (libItm.size * 1) - libDlQueue.push(libItm) - } - } - cb() - }, (err) => { - this.libraries = new DLTracker(libDlQueue, dlSize) - resolve(undefined) - }) - }) - } - - // #endregion - - // Miscellaneous (Category=files) Validation Functions - // #region - - /** - * Public miscellaneous mojang file validation function. These files will be enqueued under - * the 'files' identifier. - * - * @param {MinecraftGameVersionManifest} versionData The version data for the assets. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - public async validateMiscellaneous(versionData: MinecraftGameVersionManifest): Promise { - await this.validateClient(versionData); - await this.validateLogConfig(versionData); - } - - /** - * Validate client file - artifact renamed from client.jar to '{version}'.jar. - * - * @param {MinecraftGameVersionManifest} versionData The version data for the assets. - * @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false. - * @returns {Promise.} An empty promise to indicate the async processing has completed. - */ - public async validateClient(versionData: MinecraftGameVersionManifest, force: boolean = false): Promise { - const clientData = versionData.downloads.client; - const version = versionData.id; - const targetPath = join(this.commonPath, 'versions', version); - const targetFile = version + '.jar'; - - let client = new Asset(version + ' client', clientData.sha1, clientData.size, clientData.url, join(targetPath, targetFile)); - - if (!AssetGuard.validateLocal(client.to, 'sha1', client.hash) || force) { - this.files.dlqueue.push(client); - this.files.dlsize += client.size * 1; - } - } - - /** - * Validate log config. - * - * @param {MinecraftGameVersionManifest} versionData The version data for the assets. - * @param {boolean} force Optional. If true, the asset index will be downloaded even if it exists locally. Defaults to false. - * @returns {void} An empty promise to indicate the async processing has completed. - */ - public validateLogConfig(versionData: MinecraftGameVersionManifest): void { - const client = versionData.logging.client - const file = client.file - const targetPath = join(this.commonPath, 'assets', 'log_configs') - - if (!file.id) throw new Error("No file ID"); - const logConfig = new Asset(file.id, file.sha1, file.size, file.url, join(targetPath, file.id ?? '')) - - if (!AssetGuard.validateLocal(logConfig.to, 'sha1', logConfig.hash)) { - this.files.dlqueue.push(logConfig) - this.files.dlsize += logConfig.size * 1 - } - } - - // #endregion - - // Distribution (Category=forge) Validation Functions - // #region - - /** - * Validate the distribution. - * - * @param {Server} server The Server to validate. - * @returns {Server} A promise which resolves to the server distribution object. - */ - public validateDistribution(server: Server): Server { - this.forge = this.parseDistroModules(server.modules, server.minecraftVersion, server.id); - return server; - } - - public parseDistroModules(modules: Module[], version: string, servid: string) { - let assets: DistroAsset[] = []; - let asize = 0; - for (let module of modules) { - let modArtifact = module.artifact; - let finalPath = modArtifact.path; - let distroAsset = new DistroAsset(module.identifier, modArtifact.getHash(), Number(modArtifact.size), modArtifact.getURL(), finalPath, module.type) - const validationPath = finalPath.toLowerCase().endsWith('.pack.xz') - ? finalPath.substring(0, finalPath.toLowerCase().lastIndexOf('.pack.xz')) - : finalPath - - if (!AssetGuard.validateLocal(validationPath, 'MD5', distroAsset.hash)) { - asize += distroAsset.size * 1 - assets.push(distroAsset) - if (validationPath !== finalPath) this.extractQueue.push(finalPath) - } - - //Recursively process the submodules then combine the results. - if (module.subModules != null) { - let dltrack = this.parseDistroModules(module.subModules, version, servid) - asize += dltrack.dlsize * 1 - assets = assets.concat(dltrack.dlqueue as DistroAsset[]) - } - } - - return new DLTracker(assets, asize) - } - - /** - * Loads Forge's version.json data into memory for the specified server id. - * - * @param {Server} server The Server to load Forge data for. - * @returns {Promise.} A promise which resolves to Forge's version.json data. - */ - public async loadForgeData(server: Server): Promise { - const modules = server.modules - for (let module of modules) { - const type = module.type - if (type === DistroTypes.ForgeHosted || type === DistroTypes.Forge) { - if (MinecraftUtil.isForgeGradle3(server.minecraftVersion, module.artifactVersion)) { - // Read Manifest - for (let subModule of module.subModules) { - if (subModule.type === DistroTypes.VersionManifest) { - return JSON.parse(readFileSync(subModule.artifact.getPath(), 'utf-8')) - } - } - throw new Error('No forge version manifest found!') - } else { - const modArtifact = module.artifact - const artifactPath = modArtifact.getPath() - const asset = new DistroAsset(module.identifier, modArtifact.getHash(), Number(modArtifact.size), modArtifact.getURL(), artifactPath, type) - try { - let forgeData = await AssetGuard.finalizeForgeAsset(asset, this.commonPath) - return forgeData; - } catch (err) { - throw err; - } - } - } - } - throw new Error('No forge module found!') - } - - private parseForgeLibraries() { - /* TODO - * Forge asset validations are already implemented. When there's nothing much - * to work on, implement forge downloads using forge's version.json. This is to - * have the code on standby if we ever need it (since it's half implemented already). - */ - } - // #endregion - - // Java (Category=''') Validation (download) Functions - // #region - - private enqueueOpenJDK(dataDir: string, mcVersion: string) { - const major = MinecraftUtil.mcVersionAtLeast('1.17', mcVersion) ? '17' : '8' - JavaGuard.latestOpenJDK(major).then(verData => { - if (verData != null) { - - dataDir = join(dataDir, 'runtime', 'x64') - const fDir = join(dataDir, verData.name) - //TODO : Verify it doesn't break a thing - const jre = new Asset(verData.name, '', verData.size, verData.uri, fDir) - this.java = new DLTracker([jre], jre.size, (asset) => { - if (verData.name.endsWith('zip')) { - - this.extractJdkZip(asset.to, dataDir) - - } else { - // Tar.gz - let h: string; - createReadStream(asset.to) - .on('error', err => logger.error(err)) - .pipe(createGunzip()) - .on('error', err => logger.error(err)) - .pipe(extract(dataDir, { - map: (header) => { - if (h == null) { - h = header.name - } - } - })) - .on('error', err => logger.error(err)) - .on('finish', () => { - unlink(asset.to, err => { - if (err) { - logger.error(err) - } - if (h.indexOf('/') > -1) { - h = h.substring(0, h.indexOf('/')) - } - const pos = join(dataDir, h) - this.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - }) - }) - } - }) - return true; - } else { - return false; - } - }) - - } - - public async extractJdkZip(zipPath: string, runtimeDir: string) { - - const zip = new StreamZipAsync({ - file: zipPath, - storeEntries: true - }); - - let pos = '' - try { - const entries = await zip.entries() - pos = join(runtimeDir, Object.keys(entries)[0]) - - logger.info('Extracting jdk..') - await zip.extract(null, runtimeDir) - logger.info('Cleaning up..') - await remove(zipPath) - logger.info('Jdk extraction complete.') - - } catch (err) { - logger.error(err) - } finally { - zip.close() - this.emit('complete', 'java', JavaGuard.javaExecFromRoot(pos)) - } - } - - // #endregion - - // #endregion - - // Control Flow Functions - // #region - - /** - * Initiate an async download process for an AssetGuard DLTracker. - * //TODO: really ? - * @param {string} identifier The identifier of the AssetGuard DLTracker. - * @param {number} limit Optional. The number of async processes to run in parallel. - * @returns {boolean} True if the process began, otherwise false. - */ - public startAsyncProcess(identifier: string, limit: number = 5): boolean { - - const dlTracker = this[identifier] - const dlQueue = dlTracker.dlqueue - - if (dlQueue.length > 0) { - logger.info('DLQueue', dlQueue) - asyncModule.eachLimit(dlQueue, limit, (asset, cb) => { - - ensureDirSync(join(asset.to, '..')) - - const req = request(asset.from) - req.pause() - - req.on('response', (resp) => { - - if (resp.statusCode === 200) { - - let doHashCheck = false - const contentLength = parseInt(resp.headers['content-length'] ?? '') - - if (contentLength !== asset.size) { - logger.warn(`WARN: Got ${contentLength} bytes for ${asset.id}: Expected ${asset.size}`) - doHashCheck = true - - // Adjust download - this.totaldlsize -= asset.size - this.totaldlsize += contentLength - } - - const writeStream = createWriteStream(asset.to) - writeStream.on('close', () => { - if (dlTracker.callback != null) { - dlTracker.callback.apply(dlTracker, [asset, self]) - } - - if (doHashCheck) { - const isValid = AssetGuard.validateLocal(asset.to, asset.type != null ? 'md5' : 'sha1', asset.hash) - if (isValid) { - logger.warn(`Hashes match for ${asset.id}, byte mismatch is an issue in the distro index.`) - } else { - logger.error(`Hashes do not match, ${asset.id} may be corrupted.`) - } - } - cb() - }); - req.pipe(writeStream) - req.resume() - - } else { - req.abort() - logger.error(`Failed to download ${asset.id}(${typeof asset.from === 'object' ? asset.from.url : asset.from}). Response code ${resp.statusCode}`) - this.progress += asset.size * 1 - this.emit('progress', 'download', this.progress, this.totaldlsize) - cb() - } - - }) - - req.on('error', (err) => { - this.emit('error', 'download', err) - }) - - req.on('data', (chunk) => { - this.progress += chunk.length - this.emit('progress', 'download', this.progress, this.totaldlsize) - }) - - }, (err) => { - - if (err) { - logger.warn('An item in ' + identifier + ' failed to process') - } else { - logger.info('All ' + identifier + ' have been processed successfully') - } - - //this.totaldlsize -= dlTracker.dlsize - //this.progress -= dlTracker.dlsize - self[identifier] = new DLTracker([], 0) - - if (this.progress >= this.totaldlsize) { - if (this.extractQueue.length > 0) { - this.emit('progress', 'extract', 1, 1) - //this.emit('extracting') - AssetGuard.extractPackXZ(this.extractQueue, this.javaexec).then(() => { - this.extractQueue = [] - this.emit('complete', 'download') - }) - } else { - this.emit('complete', 'download') - } - } - - }) - return true - } - return false - } - - /** - * //TODO: Refacto - * This function will initiate the download processed for the specified identifiers. If no argument is - * given, all identifiers will be initiated. Note that in order for files to be processed you need to run - * the processing function corresponding to that identifier. If you run this function without processing - * the files, it is likely nothing will be enqueued in the object and processing will complete - * immediately. Once all downloads are complete, this function will fire the 'complete' event on the - * global object instance. - * - * @param {Array.<{id: string, limit: number}>} identifiers Optional. The identifiers to process and corresponding parallel async task limit. - */ - processDlQueues(identifiers = [{ id: 'assets', limit: 20 }, { id: 'libraries', limit: 5 }, { id: 'files', limit: 5 }, { id: 'forge', limit: 5 }]) { - return new Promise((resolve, _reject) => { - let shouldFire = true - - // Assign dltracking variables. - this.totaldlsize = 0 - this.progress = 0 - - for (let iden of identifiers) { - this.totaldlsize += this[iden.id].dlsize - } - - this.once('complete', () => { - resolve(undefined) - }) - - for (let iden of identifiers) { - let r = this.startAsyncProcess(iden.id, iden.limit) - if (r) shouldFire = false - } - - if (shouldFire) { - this.emit('complete', 'download') - } - }) - } - - - async validateEverything(serverid: string, dev = false) { - - try { - if (!ConfigManager.isLoaded) ConfigManager.load() - - DistroManager.setDevMode(dev) - const distroIndex = await DistroManager.pullLocal() - - const server = distroIndex.getServer(serverid) - if (!server) throw new Error(`No Such Server ${serverid}`) - // Validate Everything - - await this.validateDistribution(server) - this.emit('validate', 'distribution') - const versionData = await this.loadVersionData(server.minecraftVersion) - this.emit('validate', 'version') - await this.validateAssets(versionData) - this.emit('validate', 'assets') - await this.validateLibraries(versionData) - this.emit('validate', 'libraries') - await this.validateMiscellaneous(versionData) - this.emit('validate', 'files') - await this.processDlQueues() - //this.emit('complete', 'download') - const forgeData = await this.loadForgeData(server) - - return { - versionData, - forgeData - } - - } catch (err) { - return { - versionData: null, - forgeData: null, - error: err - } - } - - - } - - - -} \ No newline at end of file diff --git a/src/services/DiscordRichPresence.ts b/src/services/DiscordRichPresence.ts deleted file mode 100644 index a16835b7..00000000 --- a/src/services/DiscordRichPresence.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { LoggerUtil } from "helios-core/."; -import { Client } from "discord-rpc-patch"; -import RPCClient from "discord-rpc-patch/src/client"; - -const logger = LoggerUtil.getLogger('DiscordWrapper') - -export class DiscordRichPresence { - private static client?: RPCClient; - private static activity?: { - details: string, - state: string, - largeImageKey: string, - largeImageText: string, - smallImageKey: string, - smallImageText: string, - startTimestamp: number, - instance: boolean, - }; - - public static initRPC(genSettings, servSettings, initialDetails = 'Waiting for this.Client..') { - this.client = new Client({ transport: 'ipc' }) - - this.activity = { - details: initialDetails, - state: 'Server: ' + servSettings.shortId, - largeImageKey: servSettings.largeImageKey, - largeImageText: servSettings.largeImageText, - smallImageKey: genSettings.smallImageKey, - smallImageText: genSettings.smallImageText, - startTimestamp: new Date().getTime(), - instance: false - } - - this.client.on('ready', () => { - logger.info('Discord RPC Connected') - this.client.setActivity(activity) - }) - - this.client.login({ clientId: genSettings.clientId }).catch(error => { - if (error.message.includes('ENOENT')) { - logger.info('Unable to initialize Discord Rich Presence, no client detected.') - } else { - logger.info('Unable to initialize Discord Rich Presence: ' + error.message, error) - } - }) - } - - public static updateDetails(details) { - if (!this.client || !this.activity) return; - this.activity.details = details - this.client.setActivity(this.activity) - } - - public static shutdownRPC() { - if (!this.client) return - this.client.clearActivity() - this.client.destroy() - this.client = undefined - this.activity = undefined - } - -} \ No newline at end of file diff --git a/src/services/JavaGuard.ts b/src/services/JavaGuard.ts deleted file mode 100644 index 4b7a39c6..00000000 --- a/src/services/JavaGuard.ts +++ /dev/null @@ -1,729 +0,0 @@ -import * as EventEmitter from "events"; -import { LoggerUtil } from 'helios-core/.'; -import { DevUtil } from '../util/DevUtil'; -import { join } from 'path'; -import { existsSync, pathExists, readdir } from 'fs-extra'; -import Registry from "winreg"; -import { MinecraftUtil } from '../util/MinecraftUtil'; -import { exec } from "child_process"; -import nodeDiskInfo from "node-disk-info"; -import fetch from "node-fetch"; -import { AdoptiumBinary, JavaMetaObject, JavaRuntimeVersion } from '../util/JavaType'; - - -const logger = LoggerUtil.getLogger("JavaGuard"); -export class JavaGuard extends EventEmitter { - - - /** - * Fetch the last open JDK binary. - * - * HOTFIX: Uses Corretto 8 for macOS. - * See: https://github.com/dscalzi/HeliosLauncher/issues/70 - * See: https://github.com/AdoptOpenJDK/openjdk-support/issues/101 - * - * @param {string} major The major version of Java to fetch. - * - * @returns {Promise.} Promise which resolved to an object containing the JRE download data. - */ - public static latestOpenJDK(major = '8') { - return process.platform === 'darwin' ? - this.latestCorretto(major) : this.latestAdoptium(major); - } - - private static async latestAdoptium(major: string) { - const majorNum = Number(major) - const sanitizedOS = process.platform === 'win32' ? 'windows' : (process.platform === 'darwin' ? 'mac' : process.platform) - const url = `https://api.adoptium.net/v3/assets/latest/${major}/hotspot?vendor=eclipse` - - const response = await fetch(url).catch(_e => { logger.error(_e); return null }); - if (!response) return null; - const json = await response.json() as AdoptiumBinary[] - - const targetBinary = json.find(entry => { - return entry.version.major === majorNum - && entry.binary.os === sanitizedOS - && entry.binary.image_type === 'jdk' - && entry.binary.architecture === 'x64' - }); - - return targetBinary ? - { - uri: targetBinary.binary.package.link, - size: targetBinary.binary.package.size, - name: targetBinary.binary.package.name - } - : null - } - - private static async latestCorretto(major: string) { - let sanitizedOS: string, ext: string; - switch (process.platform) { - case 'win32': - sanitizedOS = 'windows' - ext = 'zip' - break - case 'darwin': - sanitizedOS = 'macos' - ext = 'tar.gz' - break - case 'linux': - sanitizedOS = 'linux' - ext = 'tar.gz' - break - default: - sanitizedOS = process.platform - ext = 'tar.gz' - break - } - - const arch = DevUtil.isARM64 ? 'aarch64' : 'x64' - const url = `https://corretto.aws/downloads/latest/amazon-corretto-${major}-${arch}-${sanitizedOS}-jdk.${ext}` - - const response = await fetch(url).catch(e => { logger.error(e); return null; }); - if (!response) return null; - - return { - uri: url, - size: Number(response.headers.get("content-length")), - name: url.substring(url.lastIndexOf('/') + 1) - } - } - - /** - * Returns the path of the OS-specific executable for the given Java - * installation. Supported OS's are win32, darwin, linux. - * - * @param {string} rootDir The root directory of the Java installation. - * @returns {string} The path to the Java executable. - */ - public static javaExecFromRoot(rootDir) { - if (process.platform === 'win32') { - return join(rootDir, 'bin', 'javaw.exe') - } else if (process.platform === 'darwin') { - return join(rootDir, 'Contents', 'Home', 'bin', 'java') - } else if (process.platform === 'linux') { - return join(rootDir, 'bin', 'java') - } - return rootDir - } - - /** - * Check to see if the given path points to a Java executable. - * - * @param {string} pth The path to check against. - * @returns {boolean} True if the path points to a Java executable, otherwise false. - */ - public static isJavaExecPath(pth: string) { - if (pth == null) { - return false - } - if (process.platform === 'win32') { - return pth.endsWith(join('bin', 'javaw.exe')) - } else if (process.platform === 'darwin') { - return pth.endsWith(join('bin', 'java')) - } else if (process.platform === 'linux') { - return pth.endsWith(join('bin', 'java')) - } - return false - } - - - /** - * Load Mojang's launcher.json file. - * - * //TODO: Import the launcher.json to have autocompletion - * - * @returns {Promise.} Promise which resolves to Mojang's launcher.json object. - */ - public static async loadMojangLauncherData() { - const response = await fetch('https://launchermeta.mojang.com/mc/launcher.json').catch(e => { logger.error(e); return null }); - if (!response) return null; - - return response.json(); - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Dynamically detects the formatting - * to use. - * - * @param {string} versionString Full version string to parse. - * @returns Object containing the version information. - */ - static parseJavaRuntimeVersion(versionString: string): JavaRuntimeVersion { - const major = versionString.split('.')[0] - return major == "1" ? - JavaGuard.parseJavaRuntimeVersion_8(versionString) - : JavaGuard.parseJavaRuntimeVersion_9(versionString) - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Uses Java 8 formatting. - * - * @param {string} versionString Full version string to parse. - * @returns Object containing the version information. - */ - public static parseJavaRuntimeVersion_8(versionString: string) { - // 1.{major}.0_{update}-b{build} - // ex. 1.8.0_152-b16 - let pts = versionString.split('-') - const build = parseInt(pts[1].substring(1)) - - pts = pts[0].split('_') - - const update = parseInt(pts[1]) - const major = parseInt(pts[0].split('.')[1]) - - return { - build, - update, - major, - minor: undefined, - revision: undefined - } - } - - /** - * Parses a **full** Java Runtime version string and resolves - * the version information. Uses Java 9+ formatting. - * - * @param {string} verString Full version string to parse. - * @returns Object containing the version information. - */ - public static parseJavaRuntimeVersion_9(verString: string) { - // {major}.{minor}.{revision}+{build} - // ex. 10.0.2+13 - let pts = verString.split('+') - const build = parseInt(pts[1]) - - pts = pts[0].split('.') - - const major = parseInt(pts[0]) - const minor = parseInt(pts[1]) - const revision = parseInt(pts[2]) - return { - build, - major, - minor, - revision - } - } - - - /** - * Checks for the presence of the environment variable JAVA_HOME. If it exits, we will check - * to see if the value points to a path which exists. If the path exits, the path is returned. - * - * @returns {string} The path defined by JAVA_HOME, if it exists. Otherwise null. - */ - private static scanJavaHome() { - const jHome = process.env.JAVA_HOME - if (!jHome) return null; - try { - let res = existsSync(jHome) - return res ? jHome : null - } catch (err) { - // Malformed JAVA_HOME property. - return null - } - } - - /** - * Scans the registry for 64-bit Java entries. The paths of each entry are added to - * a set and returned. Currently, only Java 8 (1.8) is supported. - * - * @returns {Promise.>} A promise which resolves to a set of 64-bit Java root - * paths found in the registry. - */ - private static scanRegistry(): Promise> { - return new Promise((resolve, _reject) => { - // Keys for Java v9.0.0 and later: - // 'SOFTWARE\\JavaSoft\\JRE' - // 'SOFTWARE\\JavaSoft\\JDK' - // Forge does not yet support Java 9, therefore we do not. - - // Keys for Java 1.8 and prior: - const regKeys = [ - '\\SOFTWARE\\JavaSoft\\Java Runtime Environment', - '\\SOFTWARE\\JavaSoft\\Java Development Kit' - ] - - let keysDone = 0 - - const candidates = new Set() - - for (let i = 0; i < regKeys.length; i++) { - const key = new Registry({ - hive: Registry.HKLM, - key: regKeys[i], - arch: 'x64' - }) - key.keyExists((err, exists) => { - if (exists) { - key.keys((err, javaVers) => { - if (err) { - keysDone++ - console.error(err) - - // REG KEY DONE - // DUE TO ERROR - if (keysDone === regKeys.length) { - resolve(candidates) - } - } else { - if (javaVers.length === 0) { - // REG KEY DONE - // NO SUBKEYS - keysDone++ - if (keysDone === regKeys.length) { - resolve(candidates) - } - } else { - - let numDone = 0 - - for (let j = 0; j < javaVers.length; j++) { - const javaVer = javaVers[j] - const vKey = javaVer.key.substring(javaVer.key.lastIndexOf('\\') + 1) - // Only Java 8 is supported currently. - if (parseFloat(vKey) === 1.8) { - javaVer.get('JavaHome', (err, res) => { - const jHome = res.value - if (jHome.indexOf('(x86)') === -1) { - candidates.add(jHome) - } - - // SUBKEY DONE - - numDone++ - if (numDone === javaVers.length) { - keysDone++ - if (keysDone === regKeys.length) { - resolve(candidates) - } - } - }) - } else { - - // SUBKEY DONE - // NOT JAVA 8 - - numDone++ - if (numDone === javaVers.length) { - keysDone++ - if (keysDone === regKeys.length) { - resolve(candidates) - } - } - } - } - } - } - }) - } else { - - // REG KEY DONE - // DUE TO NON-EXISTANCE - - keysDone++ - if (keysDone === regKeys.length) { - resolve(candidates) - } - } - }) - } - - }) - - } - - /** - * See if JRE exists in the Internet Plug-Ins folder. - * - * @returns {string} The path of the JRE if found, otherwise null. - */ - private static scanInternetPlugins() { - // /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java - const pth = '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin' - const res = existsSync(JavaGuard.javaExecFromRoot(pth)) - return res ? pth : null - } - - /** - * Scan a directory for root JVM folders. - * - * @param {string} scanDir The directory to scan. - * @returns {Promise.>} A promise which resolves to a set of the discovered - * root JVM folders. - */ - private static async scanFileSystem(scanDir) { - let res = new Set() - if (await pathExists(scanDir)) { - - const files = await readdir(scanDir) - for (let i = 0; i < files.length; i++) { - - const combinedPath = join(scanDir, files[i]) - const execPath = JavaGuard.javaExecFromRoot(combinedPath) - - if (await pathExists(execPath)) { - res.add(combinedPath) - } - } - } - - return res - } - - /** - * Sort an array of JVM meta objects. Best candidates are placed before all others. - * Sorts based on version and gives priority to JREs over JDKs if versions match. - * - * @param {Object[]} validArr An array of JVM meta objects. - * @returns {Object[]} A sorted array of JVM meta objects. - */ - private static sortValidJavaArray(validArr: JavaMetaObject[]) { - const retArr = validArr.sort((a, b) => { - - if (a.version.major === b.version.major) { - - if (a.version.major < 9) { - // Java 8 - if (a.version.update === b.version.update) { - if (a.version.build === b.version.build) { - - // Same version, give priority to JRE. - if (a.execPath!.toLowerCase().indexOf('jdk') > -1) { - return b.execPath!.toLowerCase().indexOf('jdk') > -1 ? 0 : 1 - } else { - return -1 - } - - } else { - return a.version.build > b.version.build ? -1 : 1 - } - } else { - return a.version.update! > b.version.update! ? -1 : 1 - } - } else { - // Java 9+ - if (a.version.minor === b.version.minor) { - if (a.version.revision === b.version.revision) { - - // Same version, give priority to JRE. - if (a.execPath!.toLowerCase().indexOf('jdk') > -1) { - return b.execPath!.toLowerCase().indexOf('jdk') > -1 ? 0 : 1 - } else { - return -1 - } - - } else { - return a.version.revision! > b.version.revision! ? -1 : 1 - } - } else { - return a.version.minor! > b.version.minor! ? -1 : 1 - } - } - - } else { - return a.version.major > b.version.major ? -1 : 1 - } - }) - - return retArr - } - - constructor(public mcVersion: string) { - super(); - - } - - /** - * Validates the output of a JVM's properties. Currently validates that a JRE is x64 - * and that the major = 8, update > 52. - * - * @param {string} stderr The output to validate. - * - * @returns {Promise.} A promise which resolves to a meta object about the JVM. - * The validity is stored inside the `valid` property. - */ - private validateJVMProperties(stderr: string) { - const res = stderr - const props = res.split('\n') - - const goal = 2 - let checksum = 0 - - const meta: any = {} - - for (let i = 0; i < props.length; i++) { - if (props[i].indexOf('sun.arch.data.model') > -1) { - const arch = props[i].split('=')[1].trim() - const parsedArch = parseInt(arch) - logger.debug(props[i].trim()) - - if (parsedArch === 64) { - meta.arch = parsedArch - ++checksum - if (checksum === goal) break; - } - - } else if (props[i].indexOf('java.runtime.version') > -1) { - let verString = props[i].split('=')[1].trim() - logger.debug(props[i].trim()) - const objectVersion = JavaGuard.parseJavaRuntimeVersion(verString) - // TODO implement a support matrix eventually. Right now this is good enough - // 1.7-1.16 = Java 8 - // 1.17+ = Java 17 - // Actual support may vary, but we're going with this rule for simplicity. - if (objectVersion.major < 9 && !MinecraftUtil.mcVersionAtLeast('1.17', this.mcVersion)) { - // Java 8 - if (objectVersion.major === 8 && objectVersion.update! > 52) { - meta.version = objectVersion - ++checksum - if (checksum === goal) break; - } - } else if (objectVersion.major >= 17 && MinecraftUtil.mcVersionAtLeast('1.17', this.mcVersion)) { - // Java 9+ - meta.version = objectVersion - ++checksum - if (checksum === goal) break; - } - // Space included so we get only the vendor. - } else if (props[i].lastIndexOf('java.vendor ') > -1) { - let vendorName = props[i].split('=')[1].trim() - logger.debug(props[i].trim()) - meta.vendor = vendorName - } else if (props[i].indexOf('os.arch') > -1) { - meta.isARM = props[i].split('=')[1].trim() === 'aarch64' - } - } - - meta.valid = checksum === goal - - return meta - } - - - /** - * Validates that a Java binary is at least 64 bit. This makes use of the non-standard - * command line option -XshowSettings:properties. The output of this contains a property, - * sun.arch.data.model = ARCH, in which ARCH is either 32 or 64. This option is supported - * in Java 8 and 9. Since this is a non-standard option. This will resolve to true if - * the function's code throws errors. That would indicate that the option is changed or - * removed. - * - * @param {string} binaryExecPath Path to the java executable we wish to validate. - * - * @returns {Promise.} A promise which resolves to a meta object about the JVM. - * The validity is stored inside the `valid` property. - */ - private validateJavaBinary(binaryExecPath): Promise { - return new Promise((resolve, _reject) => { - if (!JavaGuard.isJavaExecPath(binaryExecPath)) { - resolve(null) - } else if (existsSync(binaryExecPath)) { - // Workaround (javaw.exe no longer outputs this information.) - logger.debug(typeof binaryExecPath) - if (binaryExecPath.indexOf('javaw.exe') > -1) { - binaryExecPath.replace('javaw.exe', 'java.exe') - } - exec('"' + binaryExecPath + '" -XshowSettings:properties', (_err, _stdout, stderr) => { - try { - // Output is stored in stderr? - resolve(this.validateJVMProperties(stderr) as JavaMetaObject) - } catch (err) { - // Output format might have changed, validation cannot be completed. - resolve(null) - } - }) - } else { - resolve(null) - } - }) - - } - - /** - * - * @param {Set.} rootSet A set of JVM root strings to validate. - * @returns {Promise.} A promise which resolves to an array of meta objects - * for each valid JVM root directory. - */ - private async validateJavaRootSet(rootSet: Set) { - - const rootArr = Array.from(rootSet) - const validArr: JavaMetaObject[] = [] - - for (let i = 0; i < rootArr.length; i++) { - - const execPath = JavaGuard.javaExecFromRoot(rootArr[i]) - - let metaObj: JavaMetaObject | null = await this.validateJavaBinary(execPath); - if (!metaObj) continue; - - metaObj.execPath = execPath - validArr.push(metaObj) - } - - return validArr - - } - - /** - * Attempts to find a valid x64 installation of Java on Windows machines. - * Possible paths will be pulled from the registry and the JAVA_HOME environment - * variable. The paths will be sorted with higher versions preceeding lower, and - * JREs preceeding JDKs. The binaries at the sorted paths will then be validated. - * The first validated is returned. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - private async win32JavaValidate(dataDir: string): Promise { - - // Get possible paths from the registry. - let pathSet1 = await JavaGuard.scanRegistry() - if (pathSet1.size === 0) { - - // Do a manual file system scan of program files. - // Check all drives - const driveMounts = nodeDiskInfo.getDiskInfoSync().map(({ mounted }) => mounted) - for (const mount of driveMounts) { - pathSet1 = new Set([ - ...pathSet1, - ...(await JavaGuard.scanFileSystem(`${mount}\\Program Files\\Java`)), - ...(await JavaGuard.scanFileSystem(`${mount}\\Program Files\\Eclipse Adoptium`)), - ...(await JavaGuard.scanFileSystem(`${mount}\\Program Files\\Eclipse Foundation`)), - ...(await JavaGuard.scanFileSystem(`${mount}\\Program Files\\AdoptOpenJDK`)) - ]) - } - - } - - // Get possible paths from the data directory. - const pathSet2 = await JavaGuard.scanFileSystem(join(dataDir, 'runtime', 'x64')) - - // Merge the results. - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Validate JAVA_HOME. - const jHome = JavaGuard.scanJavaHome() - if (jHome != null && jHome.indexOf('(x86)') === -1) { - uberSet.add(jHome) - } - - let pathArr = await this.validateJavaRootSet(uberSet) - pathArr = JavaGuard.sortValidJavaArray(pathArr) - - return pathArr.length > 0 ? pathArr[0].execPath! : null; - } - - /** - * Attempts to find a valid x64 installation of Java on MacOS. - * The system JVM directory is scanned for possible installations. - * The JAVA_HOME enviroment variable and internet plugins directory - * are also scanned and validated. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - * - * Added: On the system with ARM architecture attempts to find aarch64 Java. - * - */ - private async darwinJavaValidate(dataDir: string): Promise { - - const pathSet1 = await JavaGuard.scanFileSystem('/Library/Java/JavaVirtualMachines') - const pathSet2 = await JavaGuard.scanFileSystem(join(dataDir, 'runtime', 'x64')) - - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Check Internet Plugins folder. - const iPPath = JavaGuard.scanInternetPlugins() - if (iPPath != null) { - uberSet.add(iPPath) - } - - // Check the JAVA_HOME environment variable. - let jHome = JavaGuard.scanJavaHome() - if (jHome != null) { - // Ensure we are at the absolute root. - if (jHome.includes('/Contents/Home')) { - jHome = jHome.substring(0, jHome.indexOf('/Contents/Home')) - } - uberSet.add(jHome) - } - - let pathArr = await this.validateJavaRootSet(uberSet) - pathArr = JavaGuard.sortValidJavaArray(pathArr) - - if (pathArr.length > 0) { - - // TODO Revise this a bit, seems to work for now. Discovery logic should - // probably just filter out the invalid architectures before it even - // gets to this point. - if (DevUtil.isARM64) { - return pathArr.find(({ isARM }) => isARM)?.execPath ?? null - } else { - return pathArr.find(({ isARM }) => !isARM)?.execPath ?? null - } - - } else { - return null - } - } - - - /** - * Attempts to find a valid x64 installation of Java on Linux. - * The system JVM directory is scanned for possible installations. - * The JAVA_HOME enviroment variable is also scanned and validated. - * - * Higher versions > Lower versions - * If versions are equal, JRE > JDK. - * - * @param {string} dataDir The base launcher directory. - * @returns {Promise.} A Promise which resolves to the executable path of a valid - * x64 Java installation. If none are found, null is returned. - */ - async linuxJavaValidate(dataDir: string): Promise { - - const pathSet1 = await JavaGuard.scanFileSystem('/usr/lib/jvm') - const pathSet2 = await JavaGuard.scanFileSystem(join(dataDir, 'runtime', 'x64')) - - const uberSet = new Set([...pathSet1, ...pathSet2]) - - // Validate JAVA_HOME - const jHome = JavaGuard.scanJavaHome() - if (jHome != null) { - uberSet.add(jHome) - } - - let pathArr = await this.validateJavaRootSet(uberSet) - pathArr = JavaGuard.sortValidJavaArray(pathArr) - - return pathArr.length > 0 ? pathArr[0].execPath! : null; - } - - /** - * Retrieve the path of a valid x64 Java installation. - * - * @param {string} dataDir The base launcher directory. - * @returns {string} A path to a valid x64 Java installation, null if none found. - */ - public async validateJava(dataDir) { - return this[process.platform + 'JavaValidate'](dataDir) - } - - -} \ No newline at end of file diff --git a/src/services/Logger.ts b/src/services/Logger.ts deleted file mode 100644 index 0ae76ea3..00000000 --- a/src/services/Logger.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { LoggerUtil } from "helios-core/." -export const logger = LoggerUtil.getLogger('AssetExec') \ No newline at end of file diff --git a/src/services/ProcessBuilder.ts b/src/services/ProcessBuilder.ts deleted file mode 100644 index f271c332..00000000 --- a/src/services/ProcessBuilder.ts +++ /dev/null @@ -1,880 +0,0 @@ -import { ensureDirSync, remove, writeFile, writeFileSync } from "fs-extra"; -import { join, basename } from "path"; -import { pseudoRandomBytes } from "crypto"; -import os from "os"; -import { existsSync } from "fs-extra"; -import AdmZip from "adm-zip"; -import { spawn } from "child_process"; -import { LoggerUtil } from "helios-core/."; -import { ConfigManager } from "../manager/ConfigManager"; -import { MinecraftUtil } from "../util/MinecraftUtil"; -import { DistroTypes } from "../manager/DistroManager"; -import { Library } from "../models/Library"; -import { Module } from "../models/Module"; -import { Required } from "../models/Required"; -import { ArgumentRule, MinecraftGameVersionManifest } from '../dto/Minecraft'; -import { Server } from '../models/Server'; - -const logger = LoggerUtil.getLogger('ProcessBuilder') -export default class ProcessBuilder { - - /** - * Get the platform specific classpath separator. On windows, this is a semicolon. - * On Unix, this is a colon. - * - * @returns {string} The classpath separator for the current operating system. - */ - public static get classpathSeparator() { - return process.platform === 'win32' ? ';' : ':' - } - - public static isModEnabled(modCfg, required?: Required) { - return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.isDefault() : true - } - - - public gameDir: string; - public commonDir: string; - public forgeModListFile: string; - public fmlDir: string; - public llDir: string; - public libPath: string; - - public usingLiteLoader = false; - public llPath?: string; - - constructor( - public server: Server, - public versionData: MinecraftGameVersionManifest, - public forgeData, - public authUser, - public launcherVersion - ) { - - this.gameDir = join(ConfigManager.instanceDirectory, server.id) - this.commonDir = ConfigManager.commonDirectory - this.versionData = versionData - this.forgeData = forgeData - this.authUser = authUser - this.launcherVersion = launcherVersion - this.forgeModListFile = join(this.gameDir, 'forgeMods.list') // 1.13+ - this.fmlDir = join(this.gameDir, 'forgeModList.json') - this.llDir = join(this.gameDir, 'liteloaderModList.json') - this.libPath = join(this.commonDir, 'libraries') - - } - - - /** - * Convienence method to run the functions typically used to build a process. - */ - public build() { - ensureDirSync(this.gameDir) - const tempNativePath = join(os.tmpdir(), ConfigManager.tempNativeFolder, pseudoRandomBytes(16).toString('hex')) - process.throwDeprecation = true - this.setupLiteLoader() - logger.info('Using liteloader:', this.usingLiteLoader) - const modObj = this.resolveModConfiguration(ConfigManager.getModConfigurationForServer(this.server.id).mods, this.server.modules) - - // Mod list below 1.13 - if (!MinecraftUtil.mcVersionAtLeast('1.13', this.server.minecraftVersion)) { - this.constructJSONModList('forge', modObj.forgeMods, true) - if (this.usingLiteLoader) { - this.constructJSONModList('liteloader', modObj.liteMods, true) - } - } - - const everyMods = modObj.forgeMods.concat(modObj.liteMods) - let args = this.constructJVMArguments(everyMods, tempNativePath) - - if (MinecraftUtil.mcVersionAtLeast('1.13', this.server.minecraftVersion)) { - //args = args.concat(this.constructModArguments(modObj.forgeMods)) - args = args.concat(this.constructModList(modObj.forgeMods)) - } - - logger.info('Launch Arguments:', args) - - const child = spawn(ConfigManager.getJavaExecutable(this.server.id), args, { - cwd: this.gameDir, - detached: ConfigManager.getLaunchDetached() - }) - - if (ConfigManager.getLaunchDetached()) { - child.unref() - } - - child.stdout.setEncoding('utf8') - child.stderr.setEncoding('utf8') - - child.stdout.on('data', (data) => { - data.trim().split('\n').forEach(x => console.log(`\x1b[32m[Minecraft]\x1b[0m ${x}`)) - - }) - child.stderr.on('data', (data) => { - data.trim().split('\n').forEach(x => console.log(`\x1b[31m[Minecraft]\x1b[0m ${x}`)) - }) - child.on('close', (code, signal) => { - logger.info('Exited with code', code) - remove(tempNativePath, (err) => { - if (err) { - logger.warn('Error while deleting temp dir', err) - } else { - logger.info('Temp dir deleted successfully.') - } - }) - }) - - return child - } - - /** - * Function which performs a preliminary scan of the top level - * mods. If liteloader is present here, we setup the special liteloader - * launch options. Note that liteloader is only allowed as a top level - * mod. It must not be declared as a submodule. - */ - public setupLiteLoader() { - for (let module of this.server.modules) { - if (module.type === DistroTypes.LiteLoader) { - if (!module.required.isRequired) { - const modCfg = ConfigManager.getModConfigurationForServer(this.server.id).mods - if (ProcessBuilder.isModEnabled(modCfg[module.versionlessID], module.required)) { - if (existsSync(module.artifact.getPath())) { - this.usingLiteLoader = true - this.llPath = module.artifact.getPath() - } - } - } else { - if (existsSync(module.artifact.getPath())) { - this.usingLiteLoader = true - this.llPath = module.artifact.getPath() - } - } - } - } - } - - /** - * Resolve an array of all enabled mods. These mods will be constructed into - * a mod list format and enabled at launch. - * - * @param {Object} modConfig The mod configuration object. - * @param {Array.} modules An array of modules to parse. - * @returns {{forgeMods: Array., liteMods: Array.}} An object which contains - * a list of enabled forge mods and litemods. - */ - public resolveModConfiguration(modConfig, modules: Module[]) { - let forgeMods: Module[] = [] - let liteMods: Module[] = [] - - for (let module of modules) { - const type = module.type; - if (type === DistroTypes.ForgeMod || type === DistroTypes.LiteMod || type === DistroTypes.LiteLoader) { - const isRequired = !module.required.isRequired - const isEnabled = ProcessBuilder.isModEnabled(modConfig[module.versionlessID], module.required) - if (!isRequired || (isRequired && isEnabled)) { - if (module.hasSubModules) { - const v = this.resolveModConfiguration(modConfig[module.versionlessID].mods, module.subModules) - forgeMods = forgeMods.concat(v.forgeMods) - liteMods = liteMods.concat(v.liteMods) - if (module.type === DistroTypes.LiteLoader) continue; - } - if (type === DistroTypes.ForgeMod) { - forgeMods.push(module) - } else { - liteMods.push(module) - } - } - } - } - - return { - forgeMods, - liteMods - } - } - - - /** - * Construct a mod list json object. - * - * @param {'forge' | 'liteloader'} type The mod list type to construct. - * @param {Array.} mods An array of mods to add to the mod list. - * @param {boolean} save Optional. Whether or not we should save the mod list file. - */ - public constructJSONModList(type: 'forge' | 'liteloader', mods: Module[], save = false) { - let modList: { - repositoryRoot: string, - modRef: string[] - } = { - repositoryRoot: ((type === 'forge' && this.requiresAbsolute()) ? 'absolute:' : '') + join(this.commonDir, 'modstore'), - modRef: [] - } - - const ids: string[] = [] - if (type === 'forge') { - for (let mod of mods) { - ids.push(mod.extensionlessID) - } - } else { - for (let mod of mods) { - ids.push(mod.extensionlessID + '@' + mod.artifactExt) - } - } - - modList.modRef = ids - - if (save) { - const json = JSON.stringify(modList, null, 4) - writeFileSync(type === 'forge' ? this.fmlDir : this.llDir, json, { encoding: 'utf-8' }) - } - - return modList - } - - /** - * Construct the argument array that will be passed to the JVM process. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - public constructJVMArguments(mods: Module[], tempNativePath: string): string[] { - if (MinecraftUtil.mcVersionAtLeast('1.13', this.server.minecraftVersion)) { - return this.constructJVMArguments113(mods, tempNativePath) - } else { - return this.constructJVMArguments112(mods, tempNativePath) - } - } - - - /** - * Resolve the full classpath argument list for this process. This method will resolve all Mojang-declared - * libraries as well as the libraries declared by the server. Since mods are permitted to declare libraries, - * this method requires all enabled mods as an input - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the paths of each library required by this process. - */ - public classpathArg(mods: Module[], tempNativePath: string) { - let cpArgs: string[] = [] - - if (!MinecraftUtil.mcVersionAtLeast('1.17', this.server.minecraftVersion)) { - // Add the version.jar to the classpath. - // Must not be added to the classpath for Forge 1.17+. - const version = this.versionData.id - cpArgs.push(join(this.commonDir, 'versions', version, version + '.jar')) - } - - - if (this.usingLiteLoader && this.llPath) { - cpArgs.push(this.llPath) - } - - // Resolve the Mojang declared libraries. - const mojangLibs = this.resolveMojangLibraries(tempNativePath) - - // Resolve the server declared libraries. - const servLibs = this.resolveServerLibraries(mods) - - // Merge libraries, server libs with the same - // maven identifier will override the mojang ones. - // Ex. 1.7.10 forge overrides mojang's guava with newer version. - const finalLibs = { ...mojangLibs, ...servLibs } - cpArgs = cpArgs.concat(Object.values(finalLibs)) - - this.processClassPathList(cpArgs) - - return cpArgs - } - - /** - * Construct the mod argument list for forge 1.13 - * - * @param {Array.} mods An array of mods to add to the mod list. - */ - public constructModList(mods: Module[]) { - const writeBuffer = mods.map(mod => { - return mod.extensionlessID - }).join('\n') - - if (writeBuffer) { - writeFileSync(this.forgeModListFile, writeBuffer, { encoding: 'utf-8' }) - return [ - '--fml.mavenRoots', - join('..', '..', 'common', 'modstore'), - '--fml.modLists', - this.forgeModListFile - ] - } else { - return [] - } - - } - - - - /** - * Ensure that the classpath entries all point to jar files. - * - * //TODO: WTF WHY MATE WHY ????? - * @param {Array.} classpathEntries Array of classpath entries. - */ - private processClassPathList(classpathEntries: string[]) { - const ext = '.jar' - const extLen = ext.length - for (let i = 0; i < classpathEntries.length; i++) { - const extIndex = classpathEntries[i].indexOf(ext) - if (extIndex > -1 && extIndex !== classpathEntries[i].length - extLen) { - classpathEntries[i] = classpathEntries[i].substring(0, extIndex + extLen) - } - } - } - - - /** - * Resolve the libraries declared by this server in order to add them to the classpath. - * This method will also check each enabled mod for libraries, as mods are permitted to - * declare libraries. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @returns {{[id: string]: string}} An object containing the paths of each library this server requires. - */ - private resolveServerLibraries(mods: Module[]) { - const modules: Module[] = this.server.modules; - let libs: Record = {} - - // Locate Forge/Libraries - for (let module of modules) { - const type = module.type - if (type === DistroTypes.ForgeHosted || type === DistroTypes.Library) { - libs[module.versionlessID] = module.artifact.path; - if (!module.hasSubModules) continue; - const res = this.resolveModuleLibraries(module) - if (res.length > 0) { - - //TODO: I don't understand why ? - libs = { ...libs, ...res } - } - } - } - - //Check for any libraries in our mod list. - for (let i = 0; i < mods.length; i++) { - const mod = mods[i]; - if (!mod.hasSubModules) continue; - const res = this.resolveModuleLibraries(mods[i]) - if (res.length > 0) { - //TODO: I don't understand why ? - libs = { ...libs, ...res } - } - } - - return libs - } - - /** - * Recursively resolve the path of each library required by this module. - * - * @param {Object} module A module object from the server distro index. - * @returns {Array.} An array containing the paths of each library this module requires. - */ - private resolveModuleLibraries(module: Module) { - if (!module.hasSubModules) return [] - - let libs: string[] = [] - - for (let subModule of module.subModules) { - if (subModule.type === DistroTypes.Library) { - - if (subModule.classpath) { - libs.push(subModule.artifact.path) - } - } - - // If this module has submodules, we need to resolve the libraries for those. - // To avoid unnecessary recursive calls, base case is checked here. - if (module.hasSubModules) { - const res = this.resolveModuleLibraries(subModule) - if (res.length > 0) { - libs = libs.concat(res) - } - } - } - - return libs - } - - - /** - * Resolve the libraries defined by Mojang's version data. This method will also extract - * native libraries and point to the correct location for its classpath. - * - * TODO - clean up function - * - * @param {string} tempNativePath The path to store the native libraries. - * @returns {{[id: string]: string}} An object containing the paths of each library mojang declares. - */ - private resolveMojangLibraries(tempNativePath: string) { - const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/ - let libs: Record = {} - - const libArr = this.versionData.libraries - ensureDirSync(tempNativePath) - for (let i = 0; i < libArr.length; i++) { - const lib = libArr[i] - if (Library.validateRules(lib.rules, lib.natives)) { - - // Pre-1.19 has a natives object. - if (lib.natives != null) { - // Extract the native library. - const exclusionArr: string[] = lib.extract != null ? lib.extract.exclude : ['META-INF/'] - const artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))] - - // Location of native zip. - const to = join(this.libPath, artifact.path) - - const zip = new AdmZip(to) - const zipEntries = zip.getEntries() - - // Unzip the native zip. - for (let i = 0; i < zipEntries.length; i++) { - const fileName = zipEntries[i].entryName - - let shouldExclude = false - - // Exclude noted files. - exclusionArr.forEach(exclusion => { - if (fileName.indexOf(exclusion) > -1) { - shouldExclude = true - } - }) - - // Extract the file. - if (shouldExclude) continue; - writeFile(join(tempNativePath, fileName), zipEntries[i].getData(), (err) => { - if (err) { - logger.error('Error while extracting native library:', err) - } - }) - - } - } - // 1.19+ logic - else if (lib.name.includes('natives-')) { - - const regexTest: RegExpExecArray | null = nativesRegex.exec(lib.name) - // const os = regexTest[1] - if (!regexTest) throw new Error("No RegexTest - Processor Builder"); - - const arch = regexTest[2] ?? 'x64' - - if (arch != process.arch) continue; - - // Extract the native library. - const exclusionArr: string[] = lib.extract != null ? lib.extract.exclude : ['META-INF/', '.git', '.sha1'] - const artifact = lib.downloads.artifact - - // Location of native zip. - const to = join(this.libPath, artifact.path) - - let zip = new AdmZip(to) - let zipEntries = zip.getEntries() - - // Unzip the native zip. - for (let i = 0; i < zipEntries.length; i++) { - if (zipEntries[i].isDirectory) continue; - - const fileName = zipEntries[i].entryName; - - let shouldExclude = false; - - // Exclude noted files. - exclusionArr.forEach(exclusion => { - if (fileName.indexOf(exclusion) > -1) { - shouldExclude = true - } - }) - - const extractName = fileName.includes('/') ? fileName.substring(fileName.lastIndexOf('/')) : fileName - - // Extract the file. - if (shouldExclude) continue; - - writeFile(join(tempNativePath, extractName), zipEntries[i].getData(), (err) => { - if (err) { - logger.error('Error while extracting native library:', err) - } - }) - } - } - // No natives - else { - const dlInfo = lib.downloads - const artifact = dlInfo.artifact - const to = join(this.libPath, artifact.path) - const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':')) - libs[versionIndependentId] = to - } - } - } - - return libs - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.12 and below. - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - private constructJVMArguments112(mods: Module[], tempNativePath: string) { - - let args: string[] = [] - - // Classpath Argument - args.push('-cp') - args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.classpathSeparator)) - - // Java Arguments - if (process.platform === 'darwin') { - args.push(`-Xdock:name=${ConfigManager.launcherName.replace(" ", "")}`) - args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.id)) - args.push('-Xms' + ConfigManager.getMinRAM(this.server.id)) - args = args.concat(ConfigManager.getJVMOptions(this.server.id)) - args.push('-Djava.library.path=' + tempNativePath) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Forge Arguments - args = args.concat(this.resolveForgeArgs()) - - return args - } - - /** - * Construct the argument array that will be passed to the JVM process. - * This function is for 1.13+ - * - * Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82 - * - * @param {Array.} mods An array of enabled mods which will be launched with this process. - * @param {string} tempNativePath The path to store the native libraries. - * @returns {Array.} An array containing the full JVM arguments for this process. - */ - private constructJVMArguments113(mods: Module[], tempNativePath: string): string[] { - - const argDiscovery = /\${*(.*)}/ - - // JVM Arguments First - let args = this.versionData.arguments.jvm - - // Debug securejarhandler - // args.push('-Dbsl.debug=true') - - if (this.forgeData.arguments.jvm != null) { - for (const argStr of this.forgeData.arguments.jvm) { - args.push(argStr - .replaceAll('${library_directory}', this.libPath) - .replaceAll('${classpath_separator}', ProcessBuilder.classpathSeparator) - .replaceAll('${version_name}', this.forgeData.id) - ) - } - } - - //args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml') - - // Java Arguments - if (process.platform === 'darwin') { - args.push(`-Xdock:name=${ConfigManager.launcherName.replace(" ", "")}`) - args.push('-Xdock:icon=' + join(__dirname, '..', 'images', 'minecraft.icns')) - } - args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.id)) - args.push('-Xms' + ConfigManager.getMinRAM(this.server.id)) - args = args.concat(ConfigManager.getJVMOptions(this.server.id)) - - // Main Java Class - args.push(this.forgeData.mainClass) - - // Vanilla Arguments - args = args.concat(this.versionData.arguments.game) - - for (let i = 0; i < args.length; i++) { - const argument = args[i]; - if (typeof argument === 'string') { - - if (argDiscovery.test(argument)) { - const identifier = argument.match(argDiscovery)![1] - let val: string | null = null; - switch (identifier) { - case 'auth_player_name': - val = this.authUser.displayName.trim() - break - case 'version_name': - //val = versionData.id - val = this.server.id - break - case 'game_directory': - val = this.gameDir - break - case 'assets_root': - val = join(this.commonDir, 'assets') - break - case 'assets_index_name': - val = this.versionData.assets - break - case 'auth_uuid': - val = this.authUser.uuid.trim() - break - case 'auth_access_token': - val = this.authUser.accessToken - break - case 'user_type': - val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang' - break - case 'version_type': - val = this.versionData.type - break - case 'resolution_width': - val = ConfigManager.getGameWidth().toString(); - break - case 'resolution_height': - val = ConfigManager.getGameHeight().toString(); - break - case 'natives_directory': - val = argument.replace(argDiscovery, tempNativePath) - break - case 'launcher_name': - val = argument.replace(argDiscovery, ConfigManager.launcherName) - break - case 'launcher_version': - val = argument.replace(argDiscovery, this.launcherVersion) - break - case 'classpath': - val = this.classpathArg(mods, tempNativePath).join(ProcessBuilder.classpathSeparator) - break - } - if (val) { - args[i] = val - } - } - - } else if (argument.rules != null) { - let checksum = 0 - for (let rule of argument.rules) { - if (rule.os != null) { - if (rule.os.name === Library.mojangFriendlyOS() - && (rule.os.version == null || new RegExp(rule.os.version).test(os.release()))) { - if (rule.action === 'allow') { - checksum++ - } - } else { - if (rule.action === 'disallow') { - checksum++ - } - } - } else if (rule.features != null) { - // We don't have many 'features' in the index at the moment. - // This should be fine for a while. - // TODO: Make it a bit better - if (rule.features.has_custom_resolution != null && rule.features.has_custom_resolution === true) { - if (ConfigManager.getFullscreen()) { - (args[i] as ArgumentRule).value = [ - '--fullscreen', - 'true' - ] - } - checksum++ - } - } - } - - // TODO splice not push - if (checksum === argument.rules.length) { - if (typeof argument.value === 'string') { - args[i] = argument.value - } else if (typeof argument.value === 'object') { - args.splice(i, 1, ...argument.value) - } - - // Decrement i to reprocess the resolved value - i--; - } else { - // If not whith the checksum remove the element. - args.splice(i, 1) - } - } - } - - // Autoconnect - let isAutoconnectBroken - try { - isAutoconnectBroken = MinecraftUtil.isAutoconnectBroken(this.forgeData.id.split('-')[2]) - } catch (err) { - logger.error(err) - logger.error('Forge version format changed.. assuming autoconnect works.') - logger.debug('Forge version:', this.forgeData.id) - } - - if (isAutoconnectBroken) { - logger.error('Server autoconnect disabled on Forge 1.15.2 for builds earlier than 31.2.15 due to OpenGL Stack Overflow issue.') - logger.error('Please upgrade your Forge version to at least 31.2.15!') - } else { - this.processAutoConnectArg(args) - } - - - // Forge Specific Arguments - args = args.concat(this.forgeData.arguments.game) - - // Filter null values - args = args.filter(arg => typeof arg === 'string') - - return args as string[] - } - - private lteMinorVersion(version: number) { - return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= version; - } - - - /** - * Test to see if this version of forge requires the absolute: prefix - * on the modListFile repository field. - */ - private requiresAbsolute() { - try { - if (this.lteMinorVersion(9)) { - return false - } - - const ver = this.forgeData.id.split('-')[2] - const pts = ver.split('.') - const min = [14, 23, 3, 2655] - for (let i = 0; i < pts.length; i++) { - const parsed = Number.parseInt(pts[i]) - if (parsed < min[i]) { - return false - } else if (parsed > min[i]) { - return true - } - } - } catch (err) { - // We know old forge versions follow this format. - // Error must be caused by newer version. - } - - // Equal or errored - return true - } - - /** - * Resolve the arguments required by forge. - * - * @returns {Array.} An array containing the arguments required by forge. - */ - private resolveForgeArgs() { - const mcArgs = this.forgeData.minecraftArguments.split(' ') - const argDiscovery = /\${*(.*)}/ - - // Replace the declared variables with their proper values. - for (let i = 0; i < mcArgs.length; ++i) { - if (argDiscovery.test(mcArgs[i])) { - const identifier = mcArgs[i].match(argDiscovery)[1] - let val: string | null = null - switch (identifier) { - case 'auth_player_name': - val = this.authUser.displayName.trim() - break - case 'version_name': - //val = versionData.id - val = this.server.id - break - case 'game_directory': - val = this.gameDir - break - case 'assets_root': - val = join(this.commonDir, 'assets') - break - case 'assets_index_name': - val = this.versionData.assets - break - case 'auth_uuid': - val = this.authUser.uuid.trim() - break - case 'auth_access_token': - val = this.authUser.accessToken - break - case 'user_type': - val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang' - break - case 'user_properties': // 1.8.9 and below. - val = '{}' - break - case 'version_type': - val = this.versionData.type - break - } - if (val != null) { - mcArgs[i] = val - } - } - } - - // Autoconnect to the selected server. - this.processAutoConnectArg(mcArgs) - - // Prepare game resolution - if (ConfigManager.getFullscreen()) { - mcArgs.push('--fullscreen') - mcArgs.push(true) - } else { - mcArgs.push('--width') - mcArgs.push(ConfigManager.getGameWidth()) - mcArgs.push('--height') - mcArgs.push(ConfigManager.getGameHeight()) - } - - // Mod List File Argument - mcArgs.push('--modListFile') - if (this.lteMinorVersion(9)) { - mcArgs.push(basename(this.fmlDir)) - } else { - mcArgs.push('absolute:' + this.fmlDir) - } - - - // LiteLoader - if (this.usingLiteLoader) { - mcArgs.push('--modRepo') - mcArgs.push(this.llDir) - - // Set first arg to liteloader tweak class - mcArgs.unshift('com.mumfrey.liteloader.launch.LiteLoaderTweaker') - mcArgs.unshift('--tweakClass') - } - - return mcArgs - } - - //TODO: Not a huge fan of working by reference in Typescript/JS - // Can be a bit shitty some times - private processAutoConnectArg(args: any[]) { - if (ConfigManager.getAutoConnect() && this.server.autoconnect) { - const serverURL = new URL('my://' + this.server.address) - args.push('--server') - args.push(serverURL.hostname) - if (serverURL.port) { - args.push('--port') - args.push(serverURL.port) - } - } - } - -} \ No newline at end of file diff --git a/src/services/ServerStatus.ts b/src/services/ServerStatus.ts deleted file mode 100644 index 4bc6acc9..00000000 --- a/src/services/ServerStatus.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as net from "net"; - -export class ServerStatus { - - /** - * Retrieves the status of a minecraft server. - * - * @param {string} address The server address. - * @param {number} port Optional. The port of the server. Defaults to 25565. - * @returns {Promise.} A promise which resolves to an object containing - * status information. - */ - public static getStatus(address: string, port: number = 25565) { - - if (port === null || typeof port !== 'number') { - port = 25565 - } - - return new Promise((resolve, reject) => { - const socket = net.connect(port, address, () => { - let buff = Buffer.from([0xFE, 0x01]) - socket.write(buff) - }) - - socket.setTimeout(2500, () => { - socket.end() - reject({ - code: 'ETIMEDOUT', - errno: 'ETIMEDOUT', - address, - port - }) - }) - - socket.on('data', (data) => { - if (data != null && data.length > 0) { - let server_info = data.toString().split('\x00\x00\x00') - const NUM_FIELDS = 6 - if (server_info != null && server_info.length >= NUM_FIELDS) { - resolve({ - online: true, - version: server_info[2].replace(/\u0000/g, ''), - motd: server_info[3].replace(/\u0000/g, ''), - onlinePlayers: server_info[4].replace(/\u0000/g, ''), - maxPlayers: server_info[5].replace(/\u0000/g, '') - }) - } else { - resolve({ - online: false - }) - } - } - socket.end() - }) - - socket.on('error', (err) => { - socket.destroy() - reject(err) - // ENOTFOUND = Unable to resolve. - // ECONNREFUSED = Unable to connect to port. - }) - }) - - - } -} \ No newline at end of file diff --git a/src/util/DevUtil.ts b/src/util/DevUtil.ts deleted file mode 100644 index d8f0bc88..00000000 --- a/src/util/DevUtil.ts +++ /dev/null @@ -1,18 +0,0 @@ -const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV ?? "", 10) === 1 -const isEnvSet = 'ELECTRON_IS_DEV' in process.env - -export class DevUtil { - private static enforceDevMode = false; - - public static get IsDev() { - //@ts-ignore - return this.enforceDevMode ?? isEnvSet ? getFromEnv : (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath)) - } - - public static set IsDev(value) { - this.enforceDevMode = value; - } - - public static get isARM64() { return process.arch === 'arm64' } - -} \ No newline at end of file diff --git a/src/util/DistroTypes.ts b/src/util/DistroTypes.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/util/JavaType.ts b/src/util/JavaType.ts deleted file mode 100644 index 560da8f5..00000000 --- a/src/util/JavaType.ts +++ /dev/null @@ -1,49 +0,0 @@ -export type AdoptiumBinary = { - binary: { - architecture: string, - download_count: number, - heap_size: string, - image_type: string, - jvm_impl: string, - os: string, - package: { - checksum: string, - checksum_link: string, - download_count: number, - link: string, - metadata_link: string, - name: string, - signature_link: string, - size: number - }, - project: string, - scm_ref: string, - updated_at: string - } - release_link: string, - release_name: string, - vendor: string, - version: { - build: number, - major: number, - minor: number, - openjdk_version: string, - security: number, - semver: string - } -} - -export type JavaRuntimeVersion = { - build: number, - major: number, - minor?: number, - revision?: number, - update?: number, - execPath?: string, -} - -export type JavaMetaObject = { - execPath?: string, - version: JavaRuntimeVersion, - isARM?: boolean, -} \ No newline at end of file diff --git a/src/util/MinecraftUtil.ts b/src/util/MinecraftUtil.ts deleted file mode 100644 index e7b5096b..00000000 --- a/src/util/MinecraftUtil.ts +++ /dev/null @@ -1,61 +0,0 @@ -export class MinecraftUtil { - - /** - * Returns true if the actual version is greater than - * or equal to the desired version. - * - * @param {string} desired The desired version. - * @param {string} actual The actual version. - */ - public static mcVersionAtLeast(desired: string, actual: string) { - const des = desired.split("."); - const act = actual.split("."); - - for (let i = 0; i < des.length; i++) { - if (!(parseInt(act[i]) >= parseInt(des[i]))) { - return false; - } - } - return true; - } - - public static isForgeGradle3(mcVersion: string, forgeVersion: string) { - - if (this.mcVersionAtLeast('1.13', mcVersion)) return true; - - try { - const forgeVer = forgeVersion.split('-')[1] - const maxFG2 = [14, 23, 5, 2847] - const verSplit = forgeVer.split('.').map(v => Number(v)) - - for (let i = 0; i < maxFG2.length; i++) { - if (verSplit[i] > maxFG2[i]) { - return true - } else if (verSplit[i] < maxFG2[i]) { - return false - } - } - return false - } catch (err) { - throw new Error('Forge version is complex (changed).. launcher requires a patch.') - } - } - - public static isAutoconnectBroken(forgeVersion: string) { - const minWorking = [31, 2, 15] - const verSplit = forgeVersion.split('.').map(v => Number(v)) - - if (verSplit[0] === 31) { - for (let i = 0; i < minWorking.length; i++) { - if (verSplit[i] > minWorking[i]) { - return false - } else if (verSplit[i] < minWorking[i]) { - return true - } - } - } - - return false - } - -} diff --git a/src/util/System.ts b/src/util/System.ts deleted file mode 100644 index 3b244646..00000000 --- a/src/util/System.ts +++ /dev/null @@ -1,10 +0,0 @@ -import os from 'os'; - -export function resolveMaxRAM() { - const mem = os.totalmem(); - return mem >= 8000000000 ? "4G" : mem >= 6000000000 ? "3G" : "2G"; -} - -export function resolveMinRAM() { - return resolveMaxRAM(); -} \ No newline at end of file diff --git a/src/util/isDev.ts b/src/util/isDev.ts deleted file mode 100644 index 789b9be6..00000000 --- a/src/util/isDev.ts +++ /dev/null @@ -1,15 +0,0 @@ -const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV ?? "", 10) === 1 -const isEnvSet = 'ELECTRON_IS_DEV' in process.env - -export class DevUtil { - private static enforceDevMode = false; - - public static get IsDev() { - //@ts-ignore - return this.enforceDevMode ?? isEnvSet ? getFromEnv : (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath)) - } - - public static set IsDev(value) { - this.enforceDevMode = value; - } -} \ No newline at end of file diff --git a/src/views/app.ejs b/src/views/app.ejs deleted file mode 100644 index e829fa14..00000000 --- a/src/views/app.ejs +++ /dev/null @@ -1,55 +0,0 @@ - - - - Helios Launcher - - - - - - - <%- include('frame') %> -
- <%- include('welcome') %> - <%- include('login') %> - <%- include('waiting') %> - <%- include('loginOptions') %> - <%- include('settings') %> - <%- include('landing') %> -
- <%- include('overlay') %> -
-
-
- - -
-
-
- - - \ No newline at end of file diff --git a/src/views/frame.ejs b/src/views/frame.ejs deleted file mode 100644 index c2aaf337..00000000 --- a/src/views/frame.ejs +++ /dev/null @@ -1,33 +0,0 @@ -
-
-
-
- <%if (process.platform === 'darwin') { %> -
-
- - - -
-
- <% } else{ %> -
-
- Helios Launcher -
-
- - - -
-
- <% } %> -
-
-
\ No newline at end of file diff --git a/src/views/landing.ejs b/src/views/landing.ejs deleted file mode 100644 index 7e747818..00000000 --- a/src/views/landing.ejs +++ /dev/null @@ -1,220 +0,0 @@ - \ No newline at end of file diff --git a/src/views/login.ejs b/src/views/login.ejs deleted file mode 100644 index 7ecc4a6c..00000000 --- a/src/views/login.ejs +++ /dev/null @@ -1,65 +0,0 @@ - \ No newline at end of file diff --git a/src/views/loginOptions.ejs b/src/views/loginOptions.ejs deleted file mode 100644 index 36af37e0..00000000 --- a/src/views/loginOptions.ejs +++ /dev/null @@ -1,34 +0,0 @@ - \ No newline at end of file diff --git a/src/views/overlay.ejs b/src/views/overlay.ejs deleted file mode 100644 index 0c18aef4..00000000 --- a/src/views/overlay.ejs +++ /dev/null @@ -1,41 +0,0 @@ - \ No newline at end of file diff --git a/src/views/settings.ejs b/src/views/settings.ejs deleted file mode 100644 index aa1fa764..00000000 --- a/src/views/settings.ejs +++ /dev/null @@ -1,393 +0,0 @@ - \ No newline at end of file diff --git a/src/views/waiting.ejs b/src/views/waiting.ejs deleted file mode 100644 index 11c7e4d2..00000000 --- a/src/views/waiting.ejs +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/src/views/welcome.ejs b/src/views/welcome.ejs deleted file mode 100644 index 077bbaed..00000000 --- a/src/views/welcome.ejs +++ /dev/null @@ -1,25 +0,0 @@ - \ No newline at end of file diff --git a/src/views/welcome.ts b/src/views/welcome.ts deleted file mode 100644 index d2bd7cc5..00000000 --- a/src/views/welcome.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Script for welcome.ejs - */ -document.getElementById('welcomeButton')?.addEventListener('click', e => { - loginOptionsCancelEnabled(false) // False by default, be explicit. - loginOptionsViewOnLoginSuccess = VIEWS.landing - loginOptionsViewOnLoginCancel = VIEWS.loginOptions - switchView(VIEWS.welcome, VIEWS.loginOptions) -}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index fc7c3f2f..00000000 --- a/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "stripInternal": true, - "declaration": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "allowSyntheticDefaultImports": true, - "outDir": "./dist", - "lib": ["ES2022", "dom"], - "moduleResolution": "node" - } -}