├── .gitignore ├── src ├── renderer │ ├── lang │ │ ├── de.json │ │ ├── en.json │ │ └── ir.json │ ├── plugins │ │ ├── files.js │ │ ├── debounce.js │ │ ├── index.js │ │ ├── element-ui.js │ │ ├── i18n.js │ │ ├── git.js │ │ ├── axios.js │ │ ├── helper.js │ │ ├── ps5.js │ │ └── ps4_goldhen.js │ ├── scss │ │ ├── webfonts │ │ │ ├── fa-brands-400.eot │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-solid-900.eot │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff │ │ │ ├── fa-brands-400.woff │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.eot │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff │ │ │ ├── fa-solid-900.woff2 │ │ │ └── fa-regular-400.woff2 │ │ ├── fa │ │ │ ├── _fixed-width.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── v4-shims.scss │ │ │ ├── _animated.scss │ │ │ ├── _list.scss │ │ │ ├── _core.scss │ │ │ ├── _larger.scss │ │ │ ├── fontawesome.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _stacked.scss │ │ │ ├── brands.scss │ │ │ ├── solid.scss │ │ │ ├── regular.scss │ │ │ ├── _rotated-flipped.scss │ │ │ └── _mixins.scss │ │ ├── app.scss │ │ ├── table.scss │ │ ├── active.scss │ │ ├── reset.scss │ │ ├── fa.scss │ │ ├── default.scss │ │ ├── titlebar.scss │ │ ├── pureblack.scss │ │ └── darkmode.scss │ ├── store │ │ ├── pathify.js │ │ ├── mutation-types.js │ │ ├── modules │ │ │ ├── lang.js │ │ │ ├── auth.js │ │ │ ├── server.js │ │ │ ├── queue.js │ │ │ └── app.js │ │ └── index.js │ ├── layout │ │ └── FullLayout.vue │ ├── pages │ │ ├── app │ │ │ └── components │ │ │ │ ├── Routes.vue │ │ │ │ ├── Logs.vue │ │ │ │ ├── Files.vue │ │ │ │ └── Debug.vue │ │ ├── 404.vue │ │ ├── HBStore.vue │ │ ├── ps4 │ │ │ └── Index.vue │ │ ├── Config.vue │ │ ├── Info.vue │ │ ├── Settings.vue │ │ ├── WindowLoader.vue │ │ ├── Download.vue │ │ └── User.vue │ ├── components │ │ ├── Queue.vue │ │ ├── Tasks.vue │ │ ├── index.js │ │ ├── DownloadItem.vue │ │ ├── TitleBar.vue │ │ ├── main.vue │ │ ├── AddFileByURLDialog.vue │ │ ├── LatestVersionInfo.vue │ │ ├── DragAndDrop.vue │ │ ├── PS4Config.vue │ │ └── ServerConfig.vue │ ├── router │ │ ├── index.js │ │ └── routes.js │ ├── index.js │ └── App.vue ├── main │ ├── crashReporter.js │ ├── tray.js │ ├── updater.js │ ├── menu.js │ ├── helper.js │ └── index.js ├── test │ ├── net-send.js │ ├── net-server.js │ └── net-send-gh.js └── config │ └── links.js ├── screenshot.jpg ├── static └── assets │ ├── hb.png │ ├── rpi.png │ ├── hb_new.png │ ├── rpioop.png │ ├── rpsV2.png │ ├── ps_icon_black.png │ ├── ps_icon_white.png │ ├── ps_icon_white.psd │ ├── ps_icon_colored.png │ ├── ps_icon_white.icns │ ├── ps_icon_white@2x.png │ ├── hb-store-cdn-server.jpeg │ ├── ps_icon_white_512x512.png │ ├── ps_icon_white_converted.png │ ├── ps.svg │ ├── ps_icon_black.svg │ ├── ps_icon_white.svg │ ├── buymeashisha.svg │ ├── platform.svg │ ├── ps_icon_colored.svg │ ├── playstation_squared_icon.svg │ ├── playstation.svg │ └── ps4.svg ├── .github └── FUNDING.yml ├── package.json ├── README.md └── Troubleshoot.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | release 4 | .idea 5 | -------------------------------------------------------------------------------- /src/renderer/lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The Dash" 3 | } 4 | -------------------------------------------------------------------------------- /src/renderer/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The Dash" 3 | } 4 | -------------------------------------------------------------------------------- /src/renderer/lang/ir.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "The Dash" 3 | } 4 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /src/renderer/plugins/files.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | let f = { 4 | 5 | } 6 | 7 | export default f -------------------------------------------------------------------------------- /static/assets/hb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/hb.png -------------------------------------------------------------------------------- /static/assets/rpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/rpi.png -------------------------------------------------------------------------------- /static/assets/hb_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/hb_new.png -------------------------------------------------------------------------------- /static/assets/rpioop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/rpioop.png -------------------------------------------------------------------------------- /static/assets/rpsV2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/rpsV2.png -------------------------------------------------------------------------------- /static/assets/ps_icon_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_black.png -------------------------------------------------------------------------------- /static/assets/ps_icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_white.png -------------------------------------------------------------------------------- /static/assets/ps_icon_white.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_white.psd -------------------------------------------------------------------------------- /static/assets/ps_icon_colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_colored.png -------------------------------------------------------------------------------- /static/assets/ps_icon_white.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_white.icns -------------------------------------------------------------------------------- /static/assets/ps_icon_white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_white@2x.png -------------------------------------------------------------------------------- /src/renderer/plugins/debounce.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { debounce } from 'debounce' 3 | 4 | Vue.prototype.$debounce = debounce 5 | -------------------------------------------------------------------------------- /static/assets/hb-store-cdn-server.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/hb-store-cdn-server.jpeg -------------------------------------------------------------------------------- /static/assets/ps_icon_white_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_white_512x512.png -------------------------------------------------------------------------------- /static/assets/ps_icon_white_converted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/static/assets/ps_icon_white_converted.png -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /src/renderer/scss/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gkiokan/ps4-remote-pkg-sender/HEAD/src/renderer/scss/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /src/renderer/scss/fa/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | text-align: center; 5 | width: $fa-fw-width; 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/store/pathify.js: -------------------------------------------------------------------------------- 1 | import pathify from 'vuex-pathify' 2 | export default pathify 3 | 4 | // options 5 | pathify.options.mapping = 'simple' 6 | pathify.options.deep = 1 7 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only; } 5 | .sr-only-focusable { @include sr-only-focusable; } 6 | -------------------------------------------------------------------------------- /src/renderer/plugins/index.js: -------------------------------------------------------------------------------- 1 | import './helper' 2 | import './element-ui' 3 | import './axios' 4 | import './i18n' 5 | import './ps4' 6 | import './ps4_goldhen' 7 | import './ps5' 8 | import './git' 9 | -------------------------------------------------------------------------------- /src/renderer/plugins/element-ui.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import Element from 'element-ui' 4 | // import 'element-ui/lib/theme-chalk/index.css'; 5 | import locale from 'element-ui/lib/locale/lang/en' 6 | 7 | Vue.use(Element, { locale }) 8 | -------------------------------------------------------------------------------- /src/renderer/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import 'reset.scss'; 2 | @import 'fa.scss'; 3 | 4 | @import 'titlebar.scss'; 5 | @import 'default.scss'; 6 | @import 'table.scss'; 7 | 8 | @import 'darkmode.scss'; 9 | @import 'pureblack.scss'; 10 | 11 | @import 'active.scss'; 12 | -------------------------------------------------------------------------------- /src/main/crashReporter.js: -------------------------------------------------------------------------------- 1 | import { crashReporter } from 'electron' 2 | import windows from './index' 3 | 4 | let options = { 5 | submitURL: '', 6 | uploadToServer: false, 7 | companyName: "Gkiokan.NET", 8 | } 9 | 10 | crashReporter.start(options) 11 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/v4-shims.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @import 'variables'; 6 | @import 'shims'; 7 | -------------------------------------------------------------------------------- /src/renderer/layout/FullLayout.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /src/renderer/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | // auth.js 2 | export const LOGOUT = 'LOGOUT' 3 | export const SAVE_TOKEN = 'SAVE_TOKEN' 4 | export const FETCH_USER = 'FETCH_USER' 5 | export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS' 6 | export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE' 7 | export const UPDATE_USER = 'UPDATE_USER' 8 | 9 | // lang.js 10 | export const SET_LOCALE = 'SET_LOCALE' 11 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_animated.scss: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | animation: fa-spin 2s infinite linear; 6 | } 7 | 8 | .#{$fa-css-prefix}-pulse { 9 | animation: fa-spin 1s infinite steps(8); 10 | } 11 | 12 | @keyframes fa-spin { 13 | 0% { 14 | transform: rotate(0deg); 15 | } 16 | 17 | 100% { 18 | transform: rotate(360deg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | list-style-type: none; 6 | margin-left: $fa-li-width * 5/4; 7 | padding-left: 0; 8 | 9 | > li { position: relative; } 10 | } 11 | 12 | .#{$fa-css-prefix}-li { 13 | left: -$fa-li-width; 14 | position: absolute; 15 | text-align: center; 16 | width: $fa-li-width; 17 | line-height: inherit; 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}, 5 | .fas, 6 | .far, 7 | .fal, 8 | .fad, 9 | .fab { 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | display: inline-block; 13 | font-style: normal; 14 | font-variant: normal; 15 | text-rendering: auto; 16 | line-height: 1; 17 | } 18 | 19 | %fa-icon { 20 | @include fa-icon; 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | // makes the font 33% larger relative to the icon container 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -.0667em; 9 | } 10 | 11 | .#{$fa-css-prefix}-xs { 12 | font-size: .75em; 13 | } 14 | 15 | .#{$fa-css-prefix}-sm { 16 | font-size: .875em; 17 | } 18 | 19 | @for $i from 1 through 10 { 20 | .#{$fa-css-prefix}-#{$i}x { 21 | font-size: $i * 1em; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/pages/app/components/Routes.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/fontawesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @import 'variables'; 6 | @import 'mixins'; 7 | @import 'core'; 8 | @import 'larger'; 9 | @import 'fixed-width'; 10 | @import 'list'; 11 | @import 'bordered-pulled'; 12 | @import 'animated'; 13 | @import 'rotated-flipped'; 14 | @import 'stacked'; 15 | @import 'icons'; 16 | @import 'screen-reader'; 17 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | border: solid .08em $fa-border-color; 6 | border-radius: .1em; 7 | padding: .2em .25em .15em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix}, 14 | .fas, 15 | .far, 16 | .fal, 17 | .fab { 18 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 19 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/components/Queue.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/renderer/components/Tasks.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /src/renderer/components/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Autoload all current vue files as component and register them by their name. 3 | --- 4 | Author: Gkiokan Sali 5 | Date: 2019-05-09 6 | */ 7 | 8 | import Vue from 'vue' 9 | 10 | const requireContext = require.context('./', false, /.*\.vue$/) 11 | const layouts = requireContext.keys() 12 | .map(file => 13 | [file.replace(/(^.\/)|(\.vue$)/g, ''), requireContext(file)] 14 | ) 15 | .reduce((components, [name, component]) => { 16 | let Component = component.default || component 17 | Vue.component(Component.name, Component) 18 | }, {}) 19 | -------------------------------------------------------------------------------- /static/assets/ps.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/assets/ps_icon_black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | display: inline-block; 6 | height: 2em; 7 | line-height: 2em; 8 | position: relative; 9 | vertical-align: middle; 10 | width: ($fa-fw-width*2); 11 | } 12 | 13 | .#{$fa-css-prefix}-stack-1x, 14 | .#{$fa-css-prefix}-stack-2x { 15 | left: 0; 16 | position: absolute; 17 | text-align: center; 18 | width: 100%; 19 | } 20 | 21 | .#{$fa-css-prefix}-stack-1x { 22 | line-height: inherit; 23 | } 24 | 25 | .#{$fa-css-prefix}-stack-2x { 26 | font-size: 2em; 27 | } 28 | 29 | .#{$fa-css-prefix}-inverse { 30 | color: $fa-inverse; 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/pages/app/components/Logs.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 33 | -------------------------------------------------------------------------------- /src/renderer/pages/404.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 36 | -------------------------------------------------------------------------------- /src/renderer/store/modules/lang.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | const { locale, locales } = { locale: 'en', locales: ['de', 'en'] } 4 | 5 | // state 6 | export const state = { 7 | locale: locale, 8 | locales: locales 9 | } 10 | 11 | // getters 12 | export const getters = { 13 | locale: state => state.locale, 14 | locales: state => state.locales 15 | } 16 | 17 | // mutations 18 | export const mutations = { 19 | [types.SET_LOCALE] (state, { locale }) { 20 | state.locale = locale 21 | } 22 | } 23 | 24 | // actions 25 | export const actions = { 26 | setLocale ({ commit }, { locale }) { 27 | commit(types.SET_LOCALE, { locale }) 28 | 29 | // Cookies.set('locale', locale, { expires: 365 }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [gkiokan] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: gkiokan 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /src/renderer/plugins/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | import VueI18n from 'vue-i18n' 4 | 5 | Vue.use(VueI18n) 6 | 7 | const i18n = new VueI18n({ 8 | locale: 'en', 9 | messages: {} 10 | }) 11 | 12 | /** 13 | * @param {String} locale 14 | */ 15 | export async function loadMessages (locale) { 16 | if (Object.keys(i18n.getLocaleMessage(locale)).length === 0) { 17 | const messages = await import(/* webpackChunkName: "lang-[request]" */ `@/lang/${locale}`) 18 | i18n.setLocaleMessage(locale, messages) 19 | } 20 | 21 | if (i18n.locale !== locale) { 22 | i18n.locale = locale 23 | } 24 | } 25 | 26 | (async function () { 27 | await loadMessages(store.getters['lang/locale']) 28 | })() 29 | 30 | export default i18n 31 | -------------------------------------------------------------------------------- /src/renderer/scss/table.scss: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | // .el-divider { 4 | // margin: 3px 0px; 5 | // } 6 | 7 | small { 8 | font-size: 80%; 9 | } 10 | 11 | .file { 12 | position: relative; 13 | display: block; 14 | margin-bottom: 20px; 15 | 16 | &.border { 17 | border: 1px solid gray; 18 | } 19 | 20 | .title { 21 | display: block; 22 | min-height: 60px; 23 | } 24 | 25 | .image { 26 | display: block; 27 | width: 100%; 28 | height: 0px; 29 | padding-bottom: 100%; 30 | background: center center no-repeat; 31 | background-size: cover; 32 | } 33 | } 34 | 35 | 36 | 37 | } -------------------------------------------------------------------------------- /src/renderer/scss/active.scss: -------------------------------------------------------------------------------- 1 | html.light { 2 | .active { 3 | &.el-button { 4 | color: #888; 5 | background: transparent; 6 | border-color: #DCDFE6; 7 | } 8 | } 9 | } 10 | 11 | 12 | html { 13 | .active { 14 | 15 | &.el-button { 16 | // color: #888; 17 | // background: transparent; 18 | // border-color: #888; 19 | 20 | &:hover, &:focus { 21 | // color: inherit !important; 22 | // background-color: inherit !important; 23 | // border-color: inherit !important; 24 | } 25 | } 26 | 27 | &.el-button.el-button--success { 28 | color: #67C23A !important; 29 | background-color: transparent !important; 30 | border-color: #67C23A !important; 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/pages/HBStore.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/brands.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @import 'variables'; 6 | 7 | @font-face { 8 | font-family: 'Font Awesome 5 Brands'; 9 | font-style: normal; 10 | font-weight: 400; 11 | font-display: $fa-font-display; 12 | src: url('#{$fa-font-path}/fa-brands-400.eot'); 13 | src: url('#{$fa-font-path}/fa-brands-400.eot?#iefix') format('embedded-opentype'), 14 | url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'), 15 | url('#{$fa-font-path}/fa-brands-400.woff') format('woff'), 16 | url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'), 17 | url('#{$fa-font-path}/fa-brands-400.svg#fontawesome') format('svg'); 18 | } 19 | 20 | .fab { 21 | font-family: 'Font Awesome 5 Brands'; 22 | font-weight: 400; 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/solid.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @import 'variables'; 6 | 7 | @font-face { 8 | font-family: 'Font Awesome 5 Free'; 9 | font-style: normal; 10 | font-weight: 900; 11 | font-display: $fa-font-display; 12 | src: url('#{$fa-font-path}/fa-solid-900.eot'); 13 | src: url('#{$fa-font-path}/fa-solid-900.eot?#iefix') format('embedded-opentype'), 14 | url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'), 15 | url('#{$fa-font-path}/fa-solid-900.woff') format('woff'), 16 | url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'), 17 | url('#{$fa-font-path}/fa-solid-900.svg#fontawesome') format('svg'); 18 | } 19 | 20 | .fa, 21 | .fas { 22 | font-family: 'Font Awesome 5 Free'; 23 | font-weight: 900; 24 | } 25 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/regular.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @import 'variables'; 6 | 7 | @font-face { 8 | font-family: 'Font Awesome 5 Free'; 9 | font-style: normal; 10 | font-weight: 400; 11 | font-display: $fa-font-display; 12 | src: url('#{$fa-font-path}/fa-regular-400.eot'); 13 | src: url('#{$fa-font-path}/fa-regular-400.eot?#iefix') format('embedded-opentype'), 14 | url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'), 15 | url('#{$fa-font-path}/fa-regular-400.woff') format('woff'), 16 | url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'), 17 | url('#{$fa-font-path}/fa-regular-400.svg#fontawesome') format('svg'); 18 | } 19 | 20 | .far { 21 | font-family: 'Font Awesome 5 Free'; 22 | font-weight: 400; 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/scss/fa/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | .#{$fa-css-prefix}-flip-both, .#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(-1, -1, 2); } 11 | 12 | // Hook for IE8-9 13 | // ------------------------- 14 | 15 | :root { 16 | .#{$fa-css-prefix}-rotate-90, 17 | .#{$fa-css-prefix}-rotate-180, 18 | .#{$fa-css-prefix}-rotate-270, 19 | .#{$fa-css-prefix}-flip-horizontal, 20 | .#{$fa-css-prefix}-flip-vertical, 21 | .#{$fa-css-prefix}-flip-both { 22 | filter: none; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | import routes from './routes' 4 | import Router from 'vue-router' 5 | import { sync } from 'vuex-router-sync' 6 | 7 | Vue.use(Router) 8 | 9 | // The middleware for every page of the application. 10 | // const globalMiddleware = [] // #todo - disable for now to get performance 11 | // 12 | // // Load middleware modules dynamically. 13 | // const routeMiddleware = resolveMiddleware( 14 | // require.context('~/middleware', false, /.*\.js$/) 15 | // ) 16 | 17 | const router = createRouter() 18 | 19 | // sync(store, router) 20 | 21 | export default router 22 | 23 | /** 24 | * Create a new router instance. 25 | * 26 | * @return {Router} 27 | */ 28 | function createRouter () { 29 | const router = new Router({ 30 | // mode: 'history', 31 | base: '/', 32 | routes 33 | }) 34 | 35 | // router.beforeEach(beforeEach) 36 | // router.afterEach(afterEach) 37 | 38 | return router 39 | } 40 | -------------------------------------------------------------------------------- /src/test/net-send.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | 3 | function send(socket, packet){ 4 | return new Promise((resolve, reject) => { 5 | if( !socket ){ 6 | reject("No Socket available") 7 | } 8 | 9 | socket.write(packet) 10 | 11 | socket.on('data', (data) => { 12 | resolve(data.toString()) 13 | }) 14 | 15 | socket.on('error', (error) => { 16 | reject(error) 17 | }) 18 | }) 19 | 20 | } 21 | 22 | const socket = new net.Socket() 23 | 24 | let connectTo = { host: '127.0.0.1', port: 9090} 25 | 26 | socket.connect(connectTo, async () => { 27 | // console.log("Connected") 28 | // console.log("Send test JSON") 29 | 30 | let packet = { 31 | url: "http://127.0.0.1/random" 32 | } 33 | 34 | let data = await send(socket, JSON.stringify(packet)) 35 | 36 | console.log({ data }) 37 | 38 | // socket.end() 39 | }) 40 | 41 | socket.on('error', (err) => { 42 | console.log(err) 43 | }) -------------------------------------------------------------------------------- /src/renderer/plugins/git.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | 4 | let git = { 5 | repo: "https://api.github.com/repos/gkiokan/ps4-remote-pkg-sender/releases/latest", 6 | 7 | 8 | async getLatestRelease(){ 9 | const { data } = await axios.get(this.repo) 10 | return data 11 | }, 12 | 13 | getVersion(item){ 14 | if(!item) return null 15 | 16 | if(item.tag_name) 17 | return item.tag_name.replace('v', '') 18 | 19 | return null 20 | }, 21 | 22 | compareVersion(v1, v2) { 23 | const v1Parts = v1.split('.') 24 | const v2Parts = v2.split('.') 25 | const length = Math.max(v1Parts.length, v2Parts.length) 26 | for (let i = 0; i < length; i++) { 27 | const value = (parseInt(v1Parts[i]) || 0) - (parseInt(v2Parts[i]) || 0) 28 | if (value < 0) return -1 29 | if (value > 0) return 1 30 | } 31 | return 0 32 | }, 33 | 34 | 35 | } 36 | 37 | Vue.prototype.$git = git 38 | -------------------------------------------------------------------------------- /src/renderer/pages/app/components/Files.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | 40 | 42 | -------------------------------------------------------------------------------- /static/assets/ps_icon_white.svg: -------------------------------------------------------------------------------- 1 | 2 | Layer 3 | 7 | 8 | -------------------------------------------------------------------------------- /src/renderer/components/DownloadItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 35 | 36 | 49 | -------------------------------------------------------------------------------- /static/assets/buymeashisha.svg: -------------------------------------------------------------------------------- 1 | Buy me a Shisha on: Ko-fiBuy me a Shisha onKo-fi -------------------------------------------------------------------------------- /static/assets/platform.svg: -------------------------------------------------------------------------------- 1 | platform: windows | macos | linuxplatformwindows | macos | linux -------------------------------------------------------------------------------- /static/assets/ps_icon_colored.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/net-server.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | 3 | // header 4 | console.log("--------------------------------") 5 | console.log(" Starting Test net-server.js") 6 | console.log("--------------------------------") 7 | 8 | // create server 9 | var server = net.createServer( function(socket){ 10 | // socket.write('NET Client Server\r\n') 11 | // socket.pipe(socket) 12 | 13 | socket.on('data', (data) => { 14 | // console.log(`[Request at ${(new Date).toISOString()}]`) 15 | console.log( data.toString() ) 16 | 17 | // dynamic response 18 | let response = { 19 | res: "0", 20 | // res: "-2135809020", 21 | // res: (new Date).getTime() 22 | // nope: true, 23 | } 24 | 25 | // send response back 26 | socket.write( JSON.stringify(response) ) 27 | 28 | // static response 29 | // socket.write( JSON.stringify({ res: 0 }) ) 30 | }) 31 | }) 32 | 33 | // handle errors 34 | server.on('error', (err) => { 35 | if (err.code === 'EADDRINUSE') { 36 | console.log('Port is already in use. Abording') 37 | } else { 38 | console.error('Error occurred:', err) 39 | } 40 | }) 41 | 42 | // listen to port 43 | server.listen(9090, '127.0.0.1') -------------------------------------------------------------------------------- /src/renderer/scss/reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/renderer/scss/fa.scss: -------------------------------------------------------------------------------- 1 | // Import Font Awesome 5 Free 2 | // $fa-css-prefix: 'el-icon-fa'; 3 | // $fa-font-path: '~@fortawesome/fontawesome-free/webfonts'; 4 | $fa-font-path: './webfonts'; 5 | 6 | @import '~@fortawesome/fontawesome-free/scss/fontawesome.scss'; 7 | @import '~@fortawesome/fontawesome-free/scss/regular.scss'; 8 | @import '~@fortawesome/fontawesome-free/scss/solid.scss'; 9 | @import '~@fortawesome/fontawesome-free/scss/brands.scss'; 10 | // @import './fa/fontawesome.scss'; 11 | // @import './fa/regular.scss'; 12 | // @import './fa/solid.scss'; 13 | // @import './fa/brands.scss'; 14 | 15 | // Override Element UI's icon font 16 | .fal, 17 | .far, 18 | .fas { 19 | font-family: 'Font Awesome 5 Free' !important; 20 | } 21 | 22 | .fab { 23 | font-family: 'Font Awesome 5 Brands' !important; 24 | } 25 | 26 | .fa { 27 | width: 12px; 28 | height: 12px; 29 | font-size: 9px; 30 | line-height: 12px; 31 | } 32 | 33 | // Use Font Awesome icons without a prefix 34 | i[class^="el-icon-fa"], 35 | i[class*=" el-icon-fa"] { 36 | font-family: 'Font Awesome 5 Free' !important; // Change this to 'Font Awesome 5 Pro' for pro 37 | // font-style: normal; 38 | // font-variant: normal; 39 | // font-weight: normal; 40 | // font-size: inherit; 41 | // text-rendering: auto; 42 | // display: inline-block; 43 | // -webkit-font-smoothing: antialiased; 44 | // -moz-osx-font-smoothing: grayscale; 45 | 46 | &:before { 47 | font-size: 10px; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/config/links.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ps4_rpi: "https://pkg-zone.com/details/FLTZ00003", 3 | ps4_rpi_github: "https://github.com/flatz/ps4_remote_pkg_installer", 4 | 5 | ps4_rpioop_njzydark: "https://github.com/njzydark/PS4RPI/releases", 6 | ps4_rpioop_github_njzydark: "https://github.com/njzydark/PS4RPI", 7 | 8 | ps4_rpioop: "https://github.com/Backporter/ps4_remote_pkg_installer-OOSDK/releases", 9 | ps4_rpioop_github: "https://github.com/Backporter/ps4_remote_pkg_installer-OOSDK", 10 | 11 | github_repo: "https://github.com/Gkiokan/ps4-remote-pkg-sender", 12 | github_releases: "https://github.com/Gkiokan/ps4-remote-pkg-sender/releases", 13 | github_releases_latest: "https://github.com/Gkiokan/ps4-remote-pkg-sender/releases/latest", 14 | 15 | hb_store_cdn_server_repo: "https://github.com/Gkiokan/hb-store-cdn-server", 16 | hb_store_cdn_server_latest: "https://github.com/Gkiokan/hb-store-cdn-server/releases/latest", 17 | hb_store_cdn_server_cli_repo: "https://github.com/Gkiokan/hb-store-cdn-cli-server", 18 | 19 | report_issue: "https://github.com/Gkiokan/ps4-remote-pkg-sender/issues", 20 | troubleshoot: "https://github.com/Gkiokan/ps4-remote-pkg-sender/blob/master/Troubleshoot.md", 21 | changelog: "https://github.com/Gkiokan/ps4-remote-pkg-sender/blob/master/Changelog.md", 22 | 23 | ps4_hbstore: "https://pkg-zone.com/Store-R2.pkg", 24 | pkgzone: "https://pkg-zone.com/", 25 | kofi: "https://ko-fi.com/gkiokan", 26 | } 27 | -------------------------------------------------------------------------------- /src/test/net-send-gh.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | 3 | function send(socket, packet){ 4 | return new Promise((resolve, reject) => { 5 | if( !socket ){ 6 | reject("No Socket available") 7 | } 8 | 9 | socket.write(packet) 10 | 11 | socket.on('data', (data) => { 12 | resolve(data.toString()) 13 | }) 14 | 15 | socket.on('error', (error) => { 16 | reject(error) 17 | }) 18 | }) 19 | 20 | } 21 | 22 | const socket = new net.Socket() 23 | 24 | let connectTo = { host: '127.0.0.1', port: 9090} 25 | 26 | socket.connect(connectTo, async () => { 27 | console.log("Connecting to " + connectTo.host) 28 | console.log("Send test JSON to GoldHEN") 29 | 30 | let packet = { 31 | // url: "http://192.168.175.251:8799/Users/gkiokan/Shared/jDownloader/hb/apollo.pkg", 32 | id: "EP4064-CUSA16261_00-BLASPHEMOUS00000", 33 | contentUrl: "http://192.168.236.251:8337/dragged/Blasphemous_CUSA16261_A0105_BACKPORT_5.05_OPOISSO893.pkg", 34 | iconPath: "http://192.168.236.251:8337/dragged/Blasphemous_CUSA16261_A0105_BACKPORT_5.05_OPOISSO893.pkg/icon0.png", 35 | contentName: "Blasphemous" 36 | } 37 | 38 | console.log("Content", packet) 39 | 40 | let data = await send(socket, JSON.stringify(packet)) 41 | 42 | console.log({ data }) 43 | 44 | // socket.end() 45 | }) 46 | 47 | socket.on('error', (err) => { 48 | console.log(err) 49 | }) -------------------------------------------------------------------------------- /src/renderer/scss/fa/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon { 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | display: inline-block; 8 | font-style: normal; 9 | font-variant: normal; 10 | font-weight: normal; 11 | line-height: 1; 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | transform: rotate($degrees); 17 | } 18 | 19 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 20 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 21 | transform: scale($horiz, $vert); 22 | } 23 | 24 | 25 | // Only display content to screen readers. A la Bootstrap 4. 26 | // 27 | // See: http://a11yproject.com/posts/how-to-hide-content/ 28 | 29 | @mixin sr-only { 30 | border: 0; 31 | clip: rect(0, 0, 0, 0); 32 | height: 1px; 33 | margin: -1px; 34 | overflow: hidden; 35 | padding: 0; 36 | position: absolute; 37 | width: 1px; 38 | } 39 | 40 | // Use in conjunction with .sr-only to only display content when it's focused. 41 | // 42 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 43 | // 44 | // Credit: HTML5 Boilerplate 45 | 46 | @mixin sr-only-focusable { 47 | &:active, 48 | &:focus { 49 | clip: auto; 50 | height: auto; 51 | margin: 0; 52 | overflow: visible; 53 | position: static; 54 | width: auto; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer/pages/ps4/Index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 53 | 54 | 62 | -------------------------------------------------------------------------------- /src/main/tray.js: -------------------------------------------------------------------------------- 1 | import { app, Menu, Tray, nativeImage } from 'electron' 2 | import helper from './helper' 3 | import windows from './index' 4 | 5 | let tray 6 | 7 | function showProcessingCenter(){ 8 | windows.main.show() 9 | windows.main.webContents.send('main-route', 'home') 10 | } 11 | 12 | function showServerList(){ 13 | windows.main.show() 14 | windows.main.webContents.send('main-route', 'server') 15 | } 16 | 17 | export default { 18 | tray, 19 | 20 | createTray(){ 21 | // use https://svgtopng.com/ to convert svg to png 22 | const iconURL = helper.getIconPath() 23 | const icon = nativeImage.createFromPath(iconURL) 24 | 25 | tray = new Tray(iconURL) 26 | 27 | const contextMenu = Menu.buildFromTemplate([ 28 | { label: 'Open PS4 Remote PKG Installer', click: () => windows.main.show() }, 29 | { label: 'Open Server', click: () => windows.server.show() }, 30 | { label: 'Separator', type: 'separator'}, 31 | 32 | // { label: 'Install new PKG' }, 33 | { label: 'Show Processing Center', click: () => showProcessingCenter() }, 34 | { label: 'Show Server listed PKGs', click: () => showServerList() }, 35 | { label: 'Separator', type: 'separator'}, 36 | 37 | { label: 'PS4 API Logs', click: () => windows.ps4.show() }, 38 | { label: 'Separator', type: 'separator'}, 39 | 40 | { label: 'Info', click: () => windows.info.show() }, 41 | { label: 'Separator', type: 'separator'}, 42 | 43 | { label: "Quit Application", click: () => { app.quit() } }, 44 | ]) 45 | 46 | tray.setContextMenu(contextMenu) 47 | // tray.setTitle("PS4 RPI") 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/renderer/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | import router from './router' 5 | import store from './store' 6 | 7 | import './plugins' 8 | import './components' 9 | 10 | const styles=document.createElement('style'); 11 | styles.innerText=`@import url(https://unpkg.com/spectre.css/dist/spectre.min.css); 12 | .empty{display:flex; 13 | flex-direction:column; 14 | justify-content:center; 15 | height:100vh; 16 | position:relative}.footer{bottom:0; 17 | font-size:13px; 18 | left:50%; 19 | opacity:.9; 20 | position:absolute; 21 | transform:translateX(-50%); 22 | width:100%}`; 23 | 24 | // document.head.appendChild(styles); 25 | 26 | Vue.config.devtools = process.env.NODE_ENV !== 'production', 27 | Vue.config.productionTip = false, 28 | Vue.config.errorHandler = (error, vm, info) => { 29 | alert("Application global errorHandler:\n" + error) 30 | } 31 | 32 | new Vue({ 33 | router, 34 | store, 35 | ...App, 36 | }).$mount('#app') 37 | 38 | // global window error catcher 39 | window.onerror = function(message=null, source=null, lineno=null, colno=null, error=null) { 40 | console.log(message, source, lineno, colno, error) 41 | 42 | if(error != null) 43 | alert('Message ' + message + 44 | '\nSource ' + source + 45 | '\nLine ' + lineno + 46 | '\nColNo' + colno + 47 | '\nError ' + error) 48 | }; 49 | 50 | window.addEventListener('unhandledrejection', function(event) { 51 | //handle error here 52 | //event.promise contains the promise object 53 | //event.reason contains the reason for the rejection 54 | console.log(event) 55 | alert(event.reason) 56 | }); 57 | -------------------------------------------------------------------------------- /src/renderer/pages/app/components/Debug.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 51 | 52 | 54 | -------------------------------------------------------------------------------- /src/renderer/store/modules/auth.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import * as types from '../mutation-types' 3 | 4 | // state 5 | export const state = { 6 | user: null, 7 | token: null, 8 | } 9 | 10 | // getters 11 | export const getters = { 12 | user: state => state.user, 13 | token: state => state.token, 14 | check: state => state.user !== null 15 | } 16 | 17 | // mutations 18 | export const mutations = { 19 | [types.SAVE_TOKEN] (state, { token, remember }) { 20 | state.token = token 21 | // Cookies.set('token', token, { expires: remember ? 365 : null }) 22 | }, 23 | 24 | [types.FETCH_USER_SUCCESS] (state, { user }) { 25 | state.user = user 26 | }, 27 | 28 | [types.FETCH_USER_FAILURE] (state) { 29 | state.token = null 30 | // Cookies.remove('token') 31 | }, 32 | 33 | [types.LOGOUT] (state) { 34 | state.user = null 35 | state.token = null 36 | 37 | // Cookies.remove('token') 38 | }, 39 | 40 | [types.UPDATE_USER] (state, { user }) { 41 | state.user = user 42 | } 43 | } 44 | 45 | // actions 46 | export const actions = { 47 | saveToken ({ commit, dispatch }, payload) { 48 | commit(types.SAVE_TOKEN, payload) 49 | }, 50 | 51 | async fetchUser ({ commit }) { 52 | try { 53 | const { data } = await axios.get('/api/auth/user') 54 | 55 | commit(types.FETCH_USER_SUCCESS, { user: data }) 56 | } catch (e) { 57 | commit(types.FETCH_USER_FAILURE) 58 | } 59 | }, 60 | 61 | updateUser ({ commit }, payload) { 62 | commit(types.UPDATE_USER, payload) 63 | }, 64 | 65 | async logout ({ commit }) { 66 | try { 67 | await axios.post('/api/auth/logout') 68 | } catch (e) { } 69 | 70 | commit(types.LOGOUT) 71 | }, 72 | 73 | async fetchOauthUrl (ctx, { provider }) { 74 | const { data } = await axios.post(`/api/auth/oauth/${provider}`) 75 | 76 | return data.url 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/renderer/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import JSON5 from 'json5' 4 | 5 | // Request interceptor 6 | axios.interceptors.request.use(request => { 7 | 8 | // force to disable preflight requests 9 | // request.headers.post['method'] = 'POST' 10 | // request.headers.post['content-type'] = 'application/x-www-form-urlencoded'; 11 | // request.headers.common['content-type'] = 'application/x-www-form-urlencoded'; 12 | 13 | return request 14 | }) 15 | 16 | // Response interceptor 17 | axios.interceptors.response.use(response => 18 | { 19 | // console.log('response', response) 20 | 21 | // parse json5 22 | if(typeof response.data == 'string' && response.data.includes('0x')){ 23 | response.data = JSON5.parse(response.data) 24 | return response 25 | } 26 | 27 | // Loading.hide() 28 | return response 29 | }, 30 | 31 | // on error 32 | e => { 33 | const { status, message } = e 34 | // console.log("Axios Error", e, status, message) 35 | 36 | if(message == 'Network Error'){ 37 | console.log("Error in Response (interceptor)", message) 38 | // return Promise.reject(new Error("Network Error. Playstation not available")) 39 | // return new Promise( () => {}) 40 | 41 | return Promise.reject({ 42 | response: { message: 'Playstation not available'}, 43 | status: 4444, 44 | }) 45 | } 46 | 47 | if(message.includes('timeout of') !== false){ 48 | return Promise.reject({ 49 | response: { message: 'Timeout on Request' }, 50 | status: 4408, 51 | }) 52 | } 53 | 54 | 55 | // if (status >= 500) { 56 | // alert(message) 57 | // // #todo show error dialog 58 | // } 59 | 60 | return Promise.reject(e) 61 | }) 62 | 63 | Vue.prototype.$axios = axios 64 | 65 | export default axios 66 | -------------------------------------------------------------------------------- /src/renderer/pages/Config.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 64 | -------------------------------------------------------------------------------- /src/renderer/router/routes.js: -------------------------------------------------------------------------------- 1 | function loadTemplate(pre='', page=''){ 2 | // return () => import(__dirname + '/../pages/' + pre + page).then(m => m.default || m) 3 | return () => import('@/pages/' + pre + page).then(m => m.default || m) 4 | } 5 | 6 | function loadLayout(pre='', page=''){ 7 | // return () => import(__dirname + '/../pages/' + pre + page).then(m => m.default || m) 8 | return () => import('@/layout/' + pre + page).then(m => m.default || m) 9 | } 10 | 11 | function loadPage(page){ 12 | return loadTemplate('', page) 13 | } 14 | 15 | function loadError(page){ 16 | return loadTemplate('errors/', page) 17 | } 18 | 19 | export default [ 20 | // Default Layout 21 | { 22 | path: '/', redirect: 'home', component: loadLayout('DefaultLayout'), 23 | children: [ 24 | { path: 'home', name: 'home', component: loadPage('Index') }, 25 | { path: 'config', name: 'config', component: loadPage('Config') }, 26 | { path: 'hb-store', name: 'hb-store', component: loadPage('HBStore') }, 27 | { path: 'server', name: 'server', component: loadPage('Server') }, 28 | { path: 'settings', name: 'settings', component: loadPage('Settings') }, 29 | { path: 'changelog', name: 'changelog', component: loadPage('Changelog') }, 30 | { path: 'downloads', name: 'download', component: loadPage('Download') }, 31 | { path: 'user', name: 'user', component: loadPage('User') }, 32 | ] 33 | }, 34 | 35 | // Full Layout 36 | { 37 | path: '/', component: loadLayout('FullLayout'), 38 | children: [ 39 | { path: 'window.loader', name: 'window.loader', component: loadPage('WindowLoader') }, 40 | { path: 'info', name: 'info', component: loadPage('Info') }, 41 | { path: 'app/server', name: 'server.app', component: loadPage('app/Server') }, 42 | { path: 'ps4', name: 'ps4', component: loadPage('ps4/Index') }, 43 | ] 44 | }, 45 | 46 | // Error handling 47 | { 48 | path: '/:catchAll(.*)', name: 'NotFound', component: loadPage('404'), 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import pathify from './pathify' 4 | 5 | import { createPersistedState, createSharedMutations } from "vuex-electron" 6 | 7 | Vue.use(Vuex) 8 | 9 | // Load store modules dynamically. 10 | const requireContext = require.context('./modules', false, /.*\.js$/) 11 | 12 | const modules = requireContext.keys() 13 | .map(file => 14 | [file.replace(/(^.\/)|(\.js$)/g, ''), requireContext(file)] 15 | ) 16 | .reduce((modules, [name, module]) => { 17 | if (module.namespaced === undefined) { 18 | module.namespaced = true 19 | } 20 | 21 | return { ...modules, [name]: module } 22 | }, {}) 23 | 24 | function createStore(){ 25 | return new Vuex.Store({ 26 | plugins: [ 27 | pathify.plugin, 28 | createPersistedState({ 29 | throttle: 1000, 30 | blacklist: [ 31 | 'server/addLog', 32 | 'server/routes', 33 | 'server/setStatus', 34 | 'server/setRoutes', 35 | 'server/setServingFiles', 36 | 'server/setDraggedServingFiles' 37 | ], 38 | 39 | // whitelist: (mutation) => { 40 | // // console.log(mutation.type, mutation.payload ) 41 | // let block = ['server/addLog', 'server/routes'] 42 | 43 | // if( block.includes(mutation.type) ){ 44 | // console.info("Store::Blocking | " + mutation.type) 45 | // return false 46 | // } 47 | 48 | // console.log("Store::Pass | " + mutation.type) 49 | // return true 50 | // }, 51 | }), 52 | createSharedMutations() 53 | ], 54 | modules 55 | }) 56 | } 57 | 58 | let store 59 | 60 | while(store === undefined){ 61 | try { 62 | store = createStore() 63 | break; 64 | } 65 | catch(e){ 66 | // alert("Error in Store, guess race condition. Recreating Storage." + e) 67 | continue; 68 | } 69 | } 70 | 71 | export default store 72 | -------------------------------------------------------------------------------- /src/renderer/components/TitleBar.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 85 | 86 | 88 | -------------------------------------------------------------------------------- /src/main/updater.js: -------------------------------------------------------------------------------- 1 | import uaup from 'uaup-js' 2 | 3 | const defaultStages = { 4 | Checking: "Checking For Updates!", // When Checking For Updates. 5 | Found: "Update Found!", // If an Update is Found. 6 | NotFound: "No Update Found.", // If an Update is Not Found. 7 | Downloading: "Downloading...", // When Downloading Update. 8 | Unzipping: "Installing...", // When Unzipping the Archive into the Application Directory. 9 | Cleaning: "Finalizing...", // When Removing Temp Directories and Files (ex: update archive and tmp directory). 10 | Launch: "Launching..." // When Launching the Application. 11 | }; 12 | 13 | const updateOptions = { 14 | useGithub: true, // {Default is true} [Optional] Only Github is Currenlty Supported. 15 | gitRepo: "ps4-remote-pkg-sender", // [Required] Your Repo Name 16 | gitUsername: "gkiokan", // [Required] Your GitHub Username. 17 | isGitRepoPrivate: false, // {Default is false} [Optional] If the Repo is Private or Public (Currently not Supported). 18 | gitRepoToken: "", // {Default is null} [Optional] The Token from GitHub to Access a Private Repo. Only for Private Repos. 19 | 20 | appName: "uaup-js", //[Required] The Name of the app archive and the app folder. 21 | appExecutableName: "UAUP JS.exe", //[Required] The Executable of the Application to be Run after updating. 22 | 23 | // appDirectory: "/path/to/application", //{Default is "Application Data/AppName"} [Optional] Where the app will receide, make sure your app has permissions to be there. 24 | // versionFile: "/path/to/version.json", // {Default is "Application directory/settings/version.json"} [Optional] The Path to the Local Version File. 25 | // tempDirectory: "/tmp", // {Default is "Application directory/tmp"} [Optional] Where the Update archive will download to. 26 | 27 | progressBar: null, // {Default is null} [Optional] If Using Electron with a HTML Progressbar, use that element here, otherwise ignore 28 | label: null, // {Default is null} [Optional] If Using Electron, this will be the area where we put status updates using InnerHTML 29 | forceUpdate: false, // {Default is false} [Optional] If the Application should be forced updated. This will change to true if any errors ocurr while launching. 30 | stageTitles: defaultStages, // {Default is defaultStages} [Optional] Sets the Status Title for Each Stage 31 | }; 32 | -------------------------------------------------------------------------------- /src/renderer/pages/Info.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 81 | 82 | 84 | -------------------------------------------------------------------------------- /src/renderer/pages/Settings.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 70 | 71 | 73 | -------------------------------------------------------------------------------- /src/renderer/components/main.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 93 | -------------------------------------------------------------------------------- /src/renderer/scss/default.scss: -------------------------------------------------------------------------------- 1 | 2 | $scroll-bar-track: #eee; 3 | $scroll-bar-background: #fff; 4 | 5 | // Scroll 6 | html { 7 | scrollbar-color: #999 #333; 8 | 9 | ::-webkit-scrollbar { 10 | width: 10px; /* Mostly for vertical scrollbars */ 11 | height: 5px; /* Mostly for horizontal scrollbars */ 12 | } 13 | 14 | ::-webkit-scrollbar-thumb { /* Foreground */ 15 | background: $scroll-bar-track; 16 | } 17 | ::-webkit-scrollbar-track { /* Background */ 18 | background: $scroll-bar-background; 19 | } 20 | } 21 | 22 | html, body { 23 | background: #fff; 24 | color: #666; 25 | font-size: 15px; 26 | line-height: 1.3; 27 | font-family: verdana; 28 | // -webkit-user-select: none; 29 | // -webkit-app-region: drag; 30 | } 31 | 32 | a { 33 | text-decoration: none; 34 | } 35 | 36 | h1,h2,h3,h4,h5,h6 { 37 | font-weight: 600; 38 | line-height: 1.5; 39 | margin-bottom: 10px; 40 | color: #444 !important; 41 | } 42 | 43 | p { 44 | font-size: 15px; 45 | line-height: 1.4; 46 | color: #666; 47 | } 48 | 49 | .text-darken { 50 | color: #999; 51 | } 52 | 53 | .q-pl-md { 54 | padding-left: 40px; 55 | } 56 | 57 | table { 58 | .el-button+.el-button { 59 | margin-left: 3px; 60 | } 61 | } 62 | 63 | .el-header { 64 | position: fixed; z-index: 10; 65 | top: 0; left: 0; right: 0; 66 | } 67 | 68 | .el-main { 69 | position: relative; 70 | } 71 | 72 | .el-dialog { 73 | border-radius: 8px !important; 74 | } 75 | 76 | .el-pager li { 77 | font-weight: normal; 78 | } 79 | 80 | 81 | .base_path { 82 | width: 100%; 83 | 84 | .el-form-item__content { 85 | width: calc(100% - 190px); 86 | } 87 | } 88 | 89 | .full-width-150 { 90 | width: 100%; 91 | 92 | .el-form-item__content { 93 | width: calc(100% - 150px); 94 | } 95 | } 96 | 97 | .cursor-pointer { 98 | cursor: pointer; 99 | } 100 | 101 | .el-message-box, 102 | .el-dialog { 103 | border-radius: 8px !important; 104 | } 105 | 106 | b { 107 | font-weight: bold; 108 | } 109 | 110 | .text-center { 111 | text-align: center !important; 112 | } 113 | 114 | .text-right { 115 | text-align: right !important; 116 | } 117 | 118 | .text-left { 119 | text-align: left; 120 | } 121 | 122 | .scrollToTop { 123 | position: fixed; z-index: 20; 124 | bottom: 15px; right: 20px; 125 | } 126 | 127 | .mr-md { 128 | margin-right: 10px; 129 | } 130 | 131 | .mt-md { 132 | margin-top: 10px; 133 | } 134 | 135 | .mb-md { 136 | margin-bottom: 10px; 137 | } 138 | 139 | .text-bold { 140 | font-weight: bold; 141 | } 142 | 143 | .sync_icon .el-badge__content.is-fixed { 144 | top: 8px; right: 20px; 145 | border: none; 146 | background: maroon; 147 | } 148 | 149 | .space-30 { 150 | height: 30px; 151 | } 152 | 153 | .space-50 { 154 | height: 50px; 155 | } 156 | 157 | .base_path_input_form .el-form-item__content { 158 | line-height: 1; 159 | } -------------------------------------------------------------------------------- /src/renderer/store/modules/server.js: -------------------------------------------------------------------------------- 1 | import { make } from 'vuex-pathify' 2 | import fs from './../../plugins/fs.js' 3 | 4 | export const state = { 5 | files: [], 6 | draggedFiles: [], 7 | draggedServingFiles: [], 8 | serverFiles: [], 9 | servingFiles: [], 10 | routes: [], 11 | logs: [], 12 | status: 'stopped', 13 | loading: false, 14 | app: null, 15 | } 16 | 17 | 18 | // make all mutations 19 | export const mutations = { 20 | ...make.mutations(state), 21 | 22 | addLog(state, payload){ 23 | state.logs.unshift(payload) 24 | }, 25 | 26 | resetLogs(state){ 27 | state.logs = [] 28 | }, 29 | } 30 | 31 | // actions 32 | export const actions = { 33 | ...make.actions(state), 34 | 35 | async loadFiles({ commit, state, rootState, rootGetters }, path){ 36 | if(!path){ 37 | console.log("::store | no path given for base_path") 38 | return 39 | } 40 | 41 | // commit('loading', true) 42 | console.log("::store | Read files at base path ", path); 43 | let scan_subdir = rootGetters['app/server'].scan_subdir 44 | let files = await fs.getFilesFromBasePath(path, scan_subdir) 45 | 46 | console.log("::store | patched files", files.length) 47 | // console.log(files) 48 | commit('serverFiles', files) 49 | // commit('loading', false) 50 | }, 51 | 52 | addLog({ commit, state }, message){ 53 | // console.log(message) 54 | commit('addLog', { 55 | time: Date.now(), 56 | message, 57 | }) 58 | }, 59 | 60 | resetLogs({ commit}){ 61 | commit('resetLogs') 62 | }, 63 | startLoading({ commit }){ 64 | commit('loading', true) 65 | }, 66 | stopLoading({ commit }){ 67 | commit('loading', false) 68 | }, 69 | 70 | // prepare to handle the actions through vuex 71 | // startServer({ commit }, msg){ 72 | // ipcRenderer.send('server', 'start') 73 | // }, 74 | // stopServer({ commit }, msg){ 75 | // ipcRenderer.send('server', 'stop') 76 | // }, 77 | // toggleServer({ commit }, msg){ 78 | // ipcRenderer.send('server', 'toggle') 79 | // }, 80 | // refreshServer({ commit }, msg){ 81 | // ipcRenderer.send('server', 'refresh') 82 | // }, 83 | 84 | // addFiles({ commit, dispatch, state}, payload){ 85 | // commit('addFiles', payload) 86 | // } 87 | } 88 | 89 | // getters 90 | export const getters = { 91 | // make all getters (optional) 92 | ...make.getters(state), 93 | 94 | findFile: (state) => (file) => { 95 | if( file.type == 'dragged') 96 | return state.draggedServingFiles.find( x => x.path == file.path) 97 | 98 | return state.servingFiles.find( x => x.path == file.path) 99 | } 100 | // overwrite default `items` getter 101 | // allFiles: state => { 102 | // return state.images 103 | // }, 104 | } 105 | -------------------------------------------------------------------------------- /src/renderer/components/AddFileByURLDialog.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 79 | 80 | 93 | -------------------------------------------------------------------------------- /src/renderer/pages/WindowLoader.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 53 | 54 | 126 | -------------------------------------------------------------------------------- /src/main/menu.js: -------------------------------------------------------------------------------- 1 | import { app, shell, Menu } from 'electron' 2 | import windows from './index' 3 | import links from './../config/links' 4 | 5 | function showProcessingCenter(){ 6 | windows.main.show() 7 | windows.main.webContents.send('main-route', 'home') 8 | } 9 | 10 | function showServerList(){ 11 | windows.main.show() 12 | windows.main.webContents.send('main-route', 'server') 13 | } 14 | 15 | const application = { 16 | label: "Application", 17 | submenu: [ 18 | { label: 'Open PS4 Remote PKG Installer', click: () => windows.main.show() }, 19 | { label: 'Open Server', click: () => windows.server.show() }, 20 | { label: 'Separator', type: 'separator'}, 21 | 22 | // { label: 'Install new PKG' }, 23 | { label: 'Show Processing Center', click: () => showProcessingCenter() }, 24 | { label: 'Show Server listed PKGs', click: () => showServerList() }, 25 | { label: 'Separator', type: 'separator'}, 26 | 27 | { label: 'PS4 API Logs', click: () => windows.ps4.show() }, 28 | 29 | { label: 'Separator', type: 'separator' }, 30 | 31 | { 32 | label: "Quit", 33 | accelerator: "Command+Q", 34 | click: () => { app.quit() } 35 | }, 36 | ] 37 | } 38 | 39 | const edit = { 40 | label: "Edit", 41 | submenu: [ 42 | { 43 | label: "Undo", 44 | accelerator: "CmdOrCtrl+Z", 45 | selector: "undo:" 46 | }, 47 | { 48 | label: "Redo", 49 | accelerator: "Shift+CmdOrCtrl+Z", 50 | selector: "redo:" 51 | }, 52 | { 53 | type: "separator" 54 | }, 55 | { 56 | label: "Cut", 57 | accelerator: "CmdOrCtrl+X", 58 | selector: "cut:" 59 | }, 60 | { 61 | label: "Copy", 62 | accelerator: "CmdOrCtrl+C", 63 | selector: "copy:" 64 | }, 65 | { 66 | label: "Paste", 67 | accelerator: "CmdOrCtrl+V", 68 | selector: "paste:" 69 | }, 70 | { 71 | label: "Select All", 72 | accelerator: "CmdOrCtrl+A", 73 | selector: "selectAll:" 74 | } 75 | ] 76 | }; 77 | 78 | const help = { 79 | label: "Help", 80 | submenu: [ 81 | { 82 | label: "Info", 83 | click: () => { windows.info.show() } 84 | }, 85 | { label: 'Separator', type: 'separator' }, 86 | { 87 | label: "Changelog", 88 | click: () => { shell.openExternal(links.changelog) } 89 | }, 90 | { 91 | label: "Troubleshooting Guide", 92 | click: () => { shell.openExternal(links.troubleshoot) } 93 | }, 94 | { label: 'Separator', type: 'separator' }, 95 | { 96 | label: "GitHub", 97 | click: () => { shell.openExternal(links.github_repo) } 98 | }, 99 | { 100 | label: "Report an Issue", 101 | click: () => { shell.openExternal(links.report_issue) } 102 | } 103 | ] 104 | } 105 | 106 | const template = [ 107 | application, 108 | edit, 109 | help, 110 | ]; 111 | 112 | 113 | 114 | export default { 115 | template, 116 | createMenu(){ 117 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/renderer/store/modules/queue.js: -------------------------------------------------------------------------------- 1 | import { make } from 'vuex-pathify' 2 | 3 | export const state = { 4 | queue: [], 5 | tasks: [], 6 | installed: [], 7 | logs: [], 8 | } 9 | 10 | 11 | // make all mutations 12 | export const mutations = { 13 | ...make.mutations(state), 14 | 15 | resetAll(state){ 16 | state.queue = [] 17 | state.tasks = [] 18 | state.installed = [] 19 | state.logs = [] 20 | }, 21 | 22 | toQueue(state, file){ 23 | state.queue.push(file) 24 | }, 25 | 26 | removeQueue(state, file){ 27 | state.queue = state.queue.filter( x => x.name != file.name) 28 | }, 29 | 30 | toInstalled(state, file){ 31 | state.installed.push(file) 32 | }, 33 | 34 | status(state, { file, status }){ 35 | let i = state.queue.findIndex( x => x.name == file.name) 36 | state.queue[i].status = status 37 | }, 38 | 39 | task(state, { file, id }){ 40 | let i = state.queue.findIndex( x => x.name == file.name) 41 | state.queue[i].task = id 42 | }, 43 | 44 | addLog(state, log){ 45 | state.logs.unshift(log) 46 | }, 47 | 48 | addTask(state, task){ 49 | state.tasks.push(task) 50 | }, 51 | } 52 | 53 | // actions 54 | export const actions = { 55 | ...make.actions(state), 56 | 57 | resetAll({ commit }){ 58 | commit('resetAll') 59 | }, 60 | 61 | addToQueue({ commit }, file){ 62 | commit('toQueue', file) 63 | }, 64 | 65 | removeFromQueue({ commit }, file){ 66 | commit('removeQueue', file) 67 | }, 68 | 69 | installed({ commit, state }, file){ 70 | let i = state.installed.findIndex( x => x.name == file.name) 71 | console.log(file.name + ' installed. lets check file at pos '+ i) 72 | 73 | // file not installed yet 74 | if(i == -1){ 75 | commit('toInstalled', file) 76 | // commit('installed', [...state.installed, file]) 77 | } 78 | // file exists in installed 79 | else { 80 | console.log(file.name + ' is already installed') 81 | state.installed[i].status = 'installed +' 82 | } 83 | }, 84 | 85 | status({ commit }, data){ 86 | commit('status', data) 87 | }, 88 | 89 | task({ commit }, data){ 90 | commit('task', data) 91 | }, 92 | 93 | addLog({ commit }, log){ 94 | commit('addLog', log) 95 | }, 96 | 97 | addTask({ commit }, task){ 98 | commit('addTask', task) 99 | }, 100 | 101 | // addFiles({ commit, dispatch, state}, payload){ 102 | // commit('addFiles', payload) 103 | // } 104 | } 105 | 106 | // getters 107 | export const getters = { 108 | // make all getters (optional) 109 | ...make.getters(state), 110 | 111 | isInQueue: (state) => (file) => { 112 | // return state.queue.find( x => x.name == file.name) 113 | return state.queue.find( x => x.path == file.path) 114 | }, 115 | 116 | isInQueueUnique: (state) => (file) => { 117 | return state.queue.find( x => x.path == file.path) 118 | }, 119 | 120 | isInstalled: (state) => (file) => { 121 | return state.installed.find( x => x.path == file.path) 122 | } 123 | 124 | // overwrite default `items` getter 125 | // allFiles: state => { 126 | // return state.images 127 | // }, 128 | } 129 | -------------------------------------------------------------------------------- /src/renderer/components/LatestVersionInfo.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 91 | 92 | 109 | -------------------------------------------------------------------------------- /static/assets/playstation_squared_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/scss/titlebar.scss: -------------------------------------------------------------------------------- 1 | $titleBarHeight: 30px; 2 | $mainOffset: 60px; 3 | $titleBarColorLight: #fafafa; 4 | $titleBarColorDark: #0c0c0c; 5 | 6 | .top_right_header { 7 | position: absolute; 8 | top: 0px; right: 0px; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | height: 100%; 13 | // background: #222; 14 | 15 | > div { 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | width: 40px; height: 40px; 20 | margin-left: 10px; 21 | font-size: 20px; 22 | border-radius: 50%; 23 | cursor: pointer; 24 | 25 | &:focus { 26 | outline: none !important; 27 | } 28 | } 29 | 30 | .window_dropdown > i { 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | height: 40px; 35 | width: 40px; 36 | margin-top: 2px; 37 | 38 | &:focus { 39 | outline: none !important; 40 | } 41 | } 42 | 43 | &:focus { 44 | outline: none !important; 45 | } 46 | } 47 | 48 | .dark { 49 | .top_right_header { 50 | i { 51 | color: #bbb; 52 | } 53 | 54 | // .close_application i { 55 | // color: maroon; 56 | // } 57 | } 58 | } 59 | 60 | 61 | .alternative_title_bar { 62 | position: relative; z-index: 2; 63 | display: block; 64 | height: $titleBarHeight; 65 | line-height: $titleBarHeight; 66 | text-align: center; 67 | font-size: 13px; 68 | font-weight: bold; 69 | background: $titleBarColorLight; 70 | padding-left: 10px; 71 | padding-right: 10px; 72 | margin-left: -20px; 73 | margin-right: -20px; 74 | 75 | .title { 76 | display: block; 77 | position: fixed; z-index: 1; 78 | width: 100vw; 79 | top: 0px; left: 0px; right: 0px; 80 | } 81 | } 82 | 83 | 84 | .win, .mac { 85 | position: absolute; z-index: 5; 86 | top: 0px; 87 | display: flex; 88 | align-items: center; 89 | height: $titleBarHeight; 90 | -webkit-app-region: no-drag; 91 | 92 | > div { 93 | position: relative; 94 | z-index: 5; 95 | display: block; 96 | width: 12px; height: 12px; 97 | cursor: pointer; 98 | } 99 | } 100 | 101 | .mac { 102 | left: 10px; 103 | 104 | > div { 105 | margin-right: 10px; 106 | border-radius: 50%; 107 | background: crimson; 108 | 109 | &:nth-child(2){ 110 | background: darkorange; 111 | } 112 | 113 | &:nth-child(3){ 114 | background: forestgreen; 115 | } 116 | } 117 | 118 | &.chromatic { 119 | > div { 120 | background: #4a4a4a; 121 | 122 | &:nth-child(2){ 123 | background: #5a5a5a; 124 | } 125 | 126 | &:nth-child(3){ 127 | background: #6a6a6a; 128 | } 129 | } 130 | } 131 | } 132 | 133 | .win { 134 | right: 5px; 135 | 136 | > div { 137 | font-size: 13px; 138 | margin-left: 12px; 139 | width: 20px; 140 | height: auto; 141 | 142 | &:nth-child(3){ 143 | font-size: 16px; 144 | } 145 | } 146 | } 147 | 148 | 149 | html:not(.light){ 150 | .alternative_title_bar { 151 | background: $titleBarColorDark; 152 | color: #888; 153 | } 154 | } 155 | 156 | .draggable { 157 | -webkit-user-select: none; 158 | -webkit-app-region: drag; 159 | } 160 | 161 | .main_content_offset { 162 | display: block; 163 | height: $mainOffset + $titleBarHeight; 164 | } 165 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "2.10.4", 4 | "name": "PS4RemotePKGSenderV2", 5 | "productName": "PS4 Remote PKG Sender V2", 6 | "author": "Gkiokan ", 7 | "description": "Manage and send PKG files to your PS4 and PS5", 8 | "scripts": { 9 | "build": "electron-webpack && electron-builder --dir", 10 | "build:mac": "electron-webpack && electron-builder --mac", 11 | "build:win": "electron-webpack && electron-builder --win", 12 | "build:linux": "electron-webpack && electron-builder --linux", 13 | "build:all": "electron-webpack && electron-builder -mwl", 14 | "dist": "electron-webpack && electron-builder --dir", 15 | "publish": "electron-builder --mac --linux --win --publish onTag", 16 | "webpack": "electron-webpack", 17 | "watch": "electron-webpack dev" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.24.5", 21 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", 22 | "@babel/preset-env": "^7.24.5", 23 | "@fortawesome/fontawesome-free": "^5.15.4", 24 | "electron": "^8.5.5", 25 | "electron-builder": "^23.0.3", 26 | "electron-devtools-installer": "^3.2.0", 27 | "electron-packager": "^12.2.0", 28 | "electron-webpack": "^2.8.2", 29 | "electron-webpack-vue": "^2.4.0", 30 | "element-ui": "^2.15.6", 31 | "json5": "^2.2.0", 32 | "lodash": "^4.17.21", 33 | "node-sass": "4.14.1", 34 | "sass-loader": "10.1.1", 35 | "vue": "^2.6.14", 36 | "vue-devtools": "^5.1.4", 37 | "vue-i18n": "^8.26.7", 38 | "vue-router": "^3.4.5", 39 | "vuex": "^3.5.1", 40 | "vuex-electron": "git+https://github.com/michaeljpeake/vuex-electron.git", 41 | "vuex-pathify": "^1.4.1", 42 | "vuex-router-sync": "^5.0.0", 43 | "webpack": "~4.42.1" 44 | }, 45 | "dependencies": { 46 | "@njzy/ps4-pkg-info": "^0.1.0", 47 | "axios": "^0.24.0", 48 | "debounce": "^1.2.1", 49 | "express": "^4.17.1", 50 | "fs.promises": "^0.1.2", 51 | "net": "^1.0.2", 52 | "os": "^0.1.2", 53 | "source-map-support": "^0.5.16", 54 | "uaup-js": "0.0.22", 55 | "uuid": "^9.0.0", 56 | "webtorrent": "^0.102.4" 57 | }, 58 | "babel": { 59 | "presets": [ 60 | "@babel/preset-env" 61 | ], 62 | "plugins": [ 63 | "@babel/plugin-proposal-nullish-coalescing-operator" 64 | ] 65 | }, 66 | "build": { 67 | "appId": "gkiokan.net.ps4remotepkgsenderv2", 68 | "asar": true, 69 | "directories": { 70 | "output": "release" 71 | }, 72 | "win": { 73 | "target": [ 74 | { 75 | "target": "zip", 76 | "arch": [ 77 | "x64", 78 | "ia32" 79 | ] 80 | }, 81 | { 82 | "target": "portable", 83 | "arch": [ 84 | "x64", 85 | "ia32" 86 | ] 87 | } 88 | ] 89 | }, 90 | "linux": { 91 | "target": [ 92 | { 93 | "target": "AppImage", 94 | "arch": [ 95 | "x64", 96 | "ia32", 97 | "armv7l" 98 | ] 99 | }, 100 | { 101 | "target": "snap" 102 | }, 103 | { 104 | "target": "deb" 105 | } 106 | ] 107 | }, 108 | "portable": { 109 | "artifactName": "${productName}-${version}.exe", 110 | "unpackDirName": "PS4RemotePKGSenderV2" 111 | }, 112 | "dmg": { 113 | "title": "${productName} - ${version}", 114 | "background": "build/background.png", 115 | "contents": [ 116 | { 117 | "x": "20", 118 | "y": "100" 119 | }, 120 | { 121 | "x": "160", 122 | "y": "100", 123 | "type": "link", 124 | "path": "/Applications" 125 | } 126 | ], 127 | "window": { 128 | "width": "540", 129 | "height": "380" 130 | } 131 | }, 132 | "publish": { 133 | "provider": "github", 134 | "owner": "gkiokan", 135 | "repo": "ps4-remote-pkg-sender", 136 | "vPrefixedTagName": true 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /static/assets/playstation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import { make } from 'vuex-pathify' 2 | 3 | export const state = { 4 | time: 0, 5 | started: 0, 6 | 7 | serial: null, 8 | 9 | config: { 10 | lang: 'en', 11 | style: 'light', 12 | titleBar: 'default', 13 | useHB: false, 14 | useHBMode: 'refactored', 15 | useHBRoot: 'http://api.pkg-zone.com/', 16 | showConfigObject: false, 17 | enableExternalLinks: false, 18 | enableSystemNotifications: false, 19 | }, 20 | 21 | server: { 22 | ip: '', 23 | port: '8337', 24 | app: 'express', 25 | base_path: '', 26 | auto_scan_on_startup: true, 27 | scan_subdir: false, 28 | prependFullPath: false, 29 | enableQueueScanner: false, 30 | readSFOHeader: false, 31 | }, 32 | 33 | ps4: { 34 | ip: '', 35 | app: 'rpi', 36 | name: '', 37 | port: 12800, 38 | port_rpi: 12800, 39 | port_rpiOOP: 12800, 40 | port_ftp: 2121, 41 | port_etaHEN: 9090, 42 | port_goldhen: 9090, 43 | timeout: 2500, 44 | update: 2200, 45 | }, 46 | 47 | } 48 | 49 | 50 | // make all mutations 51 | export const mutations = { 52 | ...make.mutations(state), 53 | 54 | addTime(state){ 55 | state.time++ 56 | }, 57 | 58 | addStarted(state){ 59 | state.started++ 60 | }, 61 | 62 | resetServer(state){ 63 | state.server = { 64 | ip: '', 65 | port: '8337', 66 | app: 'express', 67 | base_path: '', 68 | auto_scan_on_startup: true, 69 | scan_subdir: false, 70 | prependFullPath: false, 71 | enableQueueScanner: false, 72 | readSFOHeader: false, 73 | } 74 | 75 | state.ps4 = { 76 | ip: '', 77 | app: 'rpi', 78 | name: '', 79 | port: 12800, 80 | port_rpi: 12800, 81 | port_rpiOOP: 12800, 82 | port_ftp: 2121, 83 | timeout: 2500, 84 | update: 2200, 85 | } 86 | }, 87 | 88 | resetConfig(state){ 89 | state.config = { 90 | lang: 'en', 91 | style: 'light', 92 | useHB: false, 93 | useHBMode: 'refactored', 94 | useHBRoot: 'http://api.pkg-zone.com/', 95 | useHBCustomRoot: '', 96 | showConfigObject: false, 97 | enableExternalLinks: false, 98 | enableSystemNotifications: false, 99 | } 100 | }, 101 | 102 | saveServer(state){ 103 | state.server = state.server 104 | }, 105 | 106 | toggleQueueScanner(state){ 107 | state.server.enableQueueScanner = !state.server.enableQueueScanner 108 | } 109 | } 110 | 111 | // actions 112 | export const actions = { 113 | ...make.actions(state), 114 | 115 | addTime( { state, commit }){ 116 | commit('addTime') 117 | }, 118 | 119 | started({commit}){ 120 | commit('addStarted') 121 | }, 122 | 123 | reset({ commit }){ 124 | commit('resetServer') 125 | }, 126 | 127 | save({ commit }){ 128 | commit('saveServer') 129 | }, 130 | 131 | resetConfig({ commit }){ 132 | commit('resetConfig') 133 | }, 134 | 135 | toggleQueueScanner({ commit }){ 136 | commit('toggleQueueScanner') 137 | } 138 | 139 | // addFiles({ commit, dispatch, state}, payload){ 140 | // commit('addFiles', payload) 141 | // } 142 | } 143 | 144 | // getters 145 | export const getters = { 146 | // make all getters (optional) 147 | ...make.getters(state), 148 | 149 | isPS5(state){ 150 | return state.ps4.app == 'etaHEN' 151 | }, 152 | 153 | getPS4TargetApp(state){ 154 | return state.ps4.app 155 | }, 156 | 157 | getPS4IP(state){ 158 | return state.ps4.ip + ':' + state.ps4.port 159 | }, 160 | 161 | getPS4Timeout(state){ 162 | return state.ps4.timeout 163 | }, 164 | 165 | getServerIP(state){ 166 | return state.server.ip + ':' + state.server.port 167 | }, 168 | 169 | getPrefixFullPath(state){ 170 | return state.server.prependFullPath 171 | }, 172 | 173 | getReadSFOHeader(state){ 174 | return state.server.readSFOHeader 175 | }, 176 | 177 | getStyle(state){ 178 | return state.config.style 179 | } 180 | 181 | // overwrite default `items` getter 182 | // allFiles: state => { 183 | // return state.images 184 | // }, 185 | } 186 | -------------------------------------------------------------------------------- /src/renderer/components/DragAndDrop.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 101 | 102 | 159 | -------------------------------------------------------------------------------- /static/assets/ps4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/plugins/helper.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import JSON5 from 'json5' 3 | 4 | let helper = { 5 | getNetWorkInterfaces() { 6 | let os = require('os'); 7 | let ifaces = []; 8 | Object.keys(os.networkInterfaces()).forEach(function (ifname) { 9 | var alias = 0; 10 | os.networkInterfaces()[ifname].forEach(function (iface) { 11 | if ('IPv4' !== iface.family || iface.internal !== false) { 12 | return; 13 | } 14 | 15 | if (alias >= 1) { 16 | ifaces.push({ 17 | title: `${ifname}-${alias}:${iface.address}`, 18 | ip: iface.address 19 | }); 20 | } else { 21 | ifaces.push({ 22 | title: `${ifname}: ${iface.address}`, 23 | ip: iface.address 24 | }); 25 | } 26 | ++alias; 27 | }); 28 | }); 29 | return ifaces; 30 | }, 31 | 32 | getServerStatusType(i=''){ 33 | if(i == 'error') 34 | return 'danger' 35 | 36 | if(i == 'running') 37 | return 'success' 38 | 39 | return '' 40 | }, 41 | 42 | getAppStoreType(type=''){ 43 | if(type == 'game' || type == 'hb game') 44 | return 'success' 45 | 46 | if(type == 'media') 47 | return 'primary' 48 | 49 | if(type == 'utility') 50 | return 'warning' 51 | 52 | if(type == 'emulator') 53 | return 'danger' 54 | 55 | return '' 56 | }, 57 | 58 | getFileStatus(type=''){ 59 | if(type == 'serving' || type == 'pause') 60 | return 'info' 61 | 62 | if(type == 'finish' || type == 'installed') 63 | return 'success' 64 | 65 | if(type == 'installing') 66 | return 'primary' 67 | 68 | if(type == 'in queue') 69 | return 'warning' 70 | 71 | return '' 72 | }, 73 | 74 | getFileSizeType(size=''){ 75 | if(size.includes('Bytes')) 76 | return 'info' 77 | 78 | if(size.includes('MB')) 79 | return 'success' 80 | 81 | if(size.includes('GB')) 82 | return 'primary' 83 | 84 | return '' 85 | }, 86 | 87 | stringifyToHex(obj){ 88 | return JSON.stringify(obj, (key, value) => { 89 | if( typeof value === 'number'){ 90 | return '0x' + value.toString(16) 91 | } 92 | return value 93 | }) 94 | }, 95 | 96 | parse(o){ 97 | return JSON5.parse(o) 98 | }, 99 | 100 | secondsToString(seconds){ 101 | if(!seconds) return '' 102 | 103 | seconds = Number(seconds); 104 | var d = Math.floor(seconds / (3600*24)); 105 | var h = Math.floor(seconds % (3600*24) / 3600); 106 | var m = Math.floor(seconds % 3600 / 60); 107 | var s = Math.floor(seconds % 60); 108 | 109 | // var dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : ""; 110 | // var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : ""; 111 | // var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : ""; 112 | // var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : ""; 113 | var dDisplay = d > 0 ? d + "d " : ""; 114 | var hDisplay = h > 0 ? h + "h " : ""; 115 | var mDisplay = m > 0 ? m + "m " : ""; 116 | var sDisplay = s > 0 ? s + "s " : ""; 117 | 118 | return dDisplay + hDisplay + mDisplay + sDisplay; 119 | }, 120 | 121 | formatBytes(bytes, decimals=2, k=1000) { 122 | if (bytes === 0) return '0 Bytes'; 123 | 124 | const dm = decimals < 0 ? 0 : decimals; 125 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 126 | 127 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 128 | 129 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 130 | }, 131 | 132 | is(val, a=true, b=false, fb=false){ 133 | if( val ) 134 | return a 135 | 136 | if (!val) 137 | return b 138 | 139 | return fb 140 | }, 141 | 142 | prettyPrint(input={}){ 143 | var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; 144 | var replacer = function(match, pIndent, pKey, pVal, pEnd) { 145 | var key = '', 146 | val = '', 147 | str = '', 148 | r = pIndent || ''; 149 | if (pKey) 150 | r = r + key + pKey.replace(/[: ]/g, '') + ': '; 151 | if (pVal) 152 | r = r + (pVal[0] == '"' ? str : val) + pVal + ''; 153 | return r + (pEnd || ''); 154 | }; 155 | 156 | return JSON.stringify(input, null, 3) 157 | .replace(/&/g, '&').replace(/\\"/g, '"') 158 | .replace(//g, '>') 159 | .replace(jsonLine, replacer); 160 | }, 161 | 162 | } 163 | 164 | Vue.prototype.$helper = helper 165 | -------------------------------------------------------------------------------- /src/renderer/pages/Download.vue: -------------------------------------------------------------------------------- 1 | 119 | 120 | 135 | 136 | 147 | -------------------------------------------------------------------------------- /src/main/helper.js: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, Menu, ipcMain, app, nativeImage } from 'electron' 2 | import path from 'path' 3 | import { format as formatUrl } from 'url' 4 | 5 | const isDevelopment = process.env.NODE_ENV !== 'production' 6 | 7 | export default { 8 | installDevtools(window){ 9 | window.webContents.on('did-frame-finish-load', () => { 10 | require('vue-devtools').install() 11 | // BrowserWindow.addDevToolsExtension('node_modules/vue-devtools/vender') 12 | window.webContents.openDevTools() 13 | }) 14 | 15 | window.webContents.on('devtools-opened', () => { 16 | window.focus() 17 | setImmediate(() => { 18 | window.focus() 19 | }) 20 | }) 21 | }, 22 | 23 | setDevtools(window){ 24 | if (isDevelopment) { 25 | this.installDevtools(window) 26 | } 27 | }, 28 | 29 | setWindowLoadURL(window, to='/'){ 30 | window.webContents.setUserAgent("StoreHAX") 31 | 32 | if (isDevelopment) { 33 | window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` + '#' + to) 34 | } 35 | else { 36 | window.loadURL('file://' + path.join(__dirname, 'index.html') + '#' + to) 37 | // window.loadURL(formatUrl({ 38 | // pathname: path.join(__dirname, 'index.html'+ '#' + to), 39 | // protocol: 'file', 40 | // slashes: true 41 | // })) 42 | } 43 | }, 44 | 45 | setErrorHandler(window){ 46 | window.onerror = (error, url, line) => { 47 | console.log(error, url, line) 48 | alert("Window Error" + error) 49 | // ipcMain.on('error', (event, data) => window.webContents.send('error', data) ) 50 | } 51 | 52 | window.webContents.on('did-fail-load', (event, errorCode, errorDescription, validateURL, isMainFrame, frameProcessId, frameRoutingId) => { 53 | console.log(event, errorCode, errorDescription, validateURL, isMainFrame, frameProcessId, frameRoutingId) 54 | alert("loading failed" + errorDescription) 55 | // ipcMain.on('error', (event, data) => window.webContents.send('error', data) ) 56 | }) 57 | }, 58 | 59 | createBaseWindow(args={}){ 60 | let params = { 61 | minHeight: 900, 62 | minWidth: 600, 63 | height: 600, 64 | width: 900, 65 | // frame: false, 66 | title: 'PS4 Remote Package Sender v2', 67 | icon: nativeImage.createFromDataURL(this.getAppIconPath()), 68 | // titleBarStyle: 'hiddenInset', 69 | webPreferences: { 70 | allowRunningInsecureContent: true, 71 | nodeIntegration: true, 72 | enableRemoteModule: true, 73 | } 74 | } 75 | 76 | if(args.width) 77 | params.minWidth = args.width 78 | 79 | if(args.height) 80 | params.minHeight = args.height 81 | 82 | return new BrowserWindow({...params, ...args}) 83 | }, 84 | 85 | createWindowInstance(to='/', args={}, debug=false){ 86 | const window = this.createBaseWindow(args) 87 | 88 | if(debug) 89 | this.setDevtools(window) 90 | 91 | this.setWindowLoadURL(window, to) 92 | 93 | this.setErrorHandler(window) 94 | 95 | return window 96 | }, 97 | 98 | autocloseAfterDownload(window){ 99 | // window.webContents.on('new-window', (createEvent, contents) => { 100 | // console.log("Web content created") 101 | // console.log(createEvent.sender) 102 | // console.log(contents) 103 | // 104 | // let newWindow = BrowserWindow.fromWebContents(createEvent.sender) 105 | // console.log(newWindow) 106 | // newWindow.setContentSize(10,10) 107 | // }) 108 | 109 | window.webContents.on('new-window', (event, url) => { 110 | console.log("Open New Window with autoclose after download") 111 | event.preventDefault() 112 | 113 | var win = new BrowserWindow({ 114 | show: true, 115 | frame: false, 116 | icon: nativeImage.createFromDataURL(this.getAppIconPath()), 117 | webPreferences: { 118 | allowRunningInsecureContent: false, 119 | nodeIntegration: true, 120 | enableRemoteModule: true, 121 | webviewTag: true, 122 | } 123 | }) 124 | 125 | win.webContents.setUserAgent("StoreHAX") 126 | win.once('ready-to-show', () => win.show()) 127 | win.loadURL(url) 128 | // win.webContents.downloadURL(url) 129 | // win.openDevTools() 130 | 131 | console.log("Set New Window url to ", url) 132 | 133 | win.webContents.session.on('will-download', (event, item, webContents) => { 134 | // console.log("Download started for ", item) 135 | item.once('done', (event, state) => { 136 | console.log("Item download state ", state) 137 | win.destroy() 138 | }) 139 | }) 140 | }) 141 | 142 | 143 | window.webContents.on('did-attach-webview', (event, webContents) => { 144 | console.log("attached new webview") 145 | }) 146 | 147 | // deprecated 148 | // window.webContents.session.on('will-download', (event, item, webContents) => { 149 | // item.once('done', (event, state) => { 150 | // BrowserWindow.fromWebContents(webContents).close(); 151 | // }); 152 | // }); 153 | }, 154 | 155 | getIconPath(){ 156 | return path.join(__static, 'assets/ps_icon_white.png') 157 | }, 158 | 159 | getAppIconPath(){ 160 | return path.join(__static, 'assets/ps_icon_white.icns') 161 | }, 162 | 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/renderer/plugins/ps5.js: -------------------------------------------------------------------------------- 1 | /* 2 | etaHEN API 3 | 4 | Send install requests 5 | curl --data '{"url":"url"} 'http://:9090' 6 | 7 | */ 8 | 9 | import Vue from 'vue' 10 | import store from '../store' 11 | import { connect } from 'http2' 12 | // import net from 'net' 13 | const net = require('net') 14 | 15 | let ps5 = { 16 | 17 | debug(){ 18 | let ps4ip = store.getters['app/getPS4IP'] 19 | console.log("Check PS IP " + ps4ip) 20 | }, 21 | 22 | // #todo refactor to ps5 23 | getURL(of='ps'){ 24 | if(of == 'url'){ 25 | return store.getters['app/getPS4IP'] 26 | } 27 | 28 | if(of == 'ps'){ 29 | let url = store.getters['app/getPS4IP'] 30 | let parts = url.split(':') 31 | return { host: parts[0], port: parts[1] } 32 | } 33 | 34 | if(of == 'server') 35 | return store.getters['app/getServerIP'] 36 | 37 | return '' 38 | }, 39 | 40 | getTimeout(min=2000){ 41 | let timeout = store.getters['app/getPS4Timeout'] 42 | return timeout < min ? min : timeout 43 | }, 44 | 45 | request( onSuccess=null, onResponse=null ){ 46 | return new Promise( (resolve, reject) => { 47 | // Set up a timeout function 48 | const handleTimeout = () => { 49 | console.error("PS5 API connection timed out"); 50 | reject("PS5 Connection timed out. PS5 not available on " + this.getURL('url') ) 51 | socket.destroy() 52 | }; 53 | 54 | // Start the timeout timer 55 | let connectionTimeout = setTimeout(handleTimeout, this.getTimeout()); 56 | 57 | // console.log("PS5 Connection to ", connectTo) 58 | const socket = new net.Socket() 59 | 60 | // Connect to the Server 61 | socket.connect(this.getURL('ps'), () => { 62 | clearTimeout(connectionTimeout) 63 | 64 | if( typeof onSuccess == 'function' ){ 65 | onSuccess(socket) 66 | } 67 | else { 68 | resolve(true) 69 | } 70 | }) 71 | 72 | // Listen for data from the Server 73 | socket.on('data', (r) => { 74 | let response = r.toString() 75 | let json = null 76 | 77 | try { 78 | console.log(json) 79 | json = JSON.parse(r) 80 | resolve(json, response) 81 | } 82 | catch(jsonError) { 83 | reject(jsonError) 84 | } 85 | }) 86 | 87 | // Handle errors 88 | socket.on('error', (err) => { 89 | clearTimeout(connectionTimeout) 90 | reject(err) 91 | }) 92 | }) 93 | }, 94 | 95 | send(requestObject={}){ 96 | return new Promise((resolve, reject) => { 97 | const client = new net.Socket(); 98 | 99 | // Set up a timeout function 100 | const timeoutId = setTimeout(() => { 101 | client.destroy() 102 | reject( new Error('PS5 Connection timed out at ' + this.getURL('url') ) ) 103 | }, this.getTimeout()); 104 | 105 | // Connect to the server 106 | client.connect(this.getURL('ps'), () => { 107 | console.log('Connected to PS5'); 108 | 109 | if( !requestObject ){ 110 | client.destroy() 111 | resolve(true) 112 | return 113 | } 114 | 115 | const jsonString = JSON.stringify(requestObject); 116 | client.write(jsonString); 117 | }); 118 | 119 | // Listen for data from the server 120 | client.on('data', (data) => { 121 | clearTimeout(timeoutId); 122 | console.log('Received data from server:', data.toString()); 123 | 124 | try { 125 | const receivedObject = JSON.parse(data); 126 | resolve(receivedObject); 127 | client.end(); 128 | } 129 | catch(e){ 130 | reject(e) 131 | } 132 | }); 133 | 134 | // Handle errors 135 | client.on('error', (error) => { 136 | clearTimeout(timeoutId); 137 | console.error('Socket error:', error); 138 | 139 | if( error.code == 'ECONNREFUSED' ) 140 | return reject(`PS5 Connection failed at ${this.getURL('url')}`) 141 | 142 | reject(error); 143 | }); 144 | 145 | // Handle socket closure 146 | client.on('close', () => { 147 | console.log("Close Connection to PS5") 148 | clearTimeout(timeoutId); 149 | }) 150 | }) 151 | }, 152 | 153 | getErrorCodeMessage(code=''){ 154 | let message = code 155 | 156 | if(code==2157510681) 157 | message = code + " | task doesn't exist (?)" 158 | 159 | if(code==2157510663) 160 | message = code + " | already installed (?)" 161 | 162 | if(code==2157510677) 163 | message = code + " | It seems to be installed already" 164 | 165 | if(code==2157510789) 166 | message = code + " | Not enough storage" 167 | 168 | return message 169 | }, 170 | 171 | checkPS5(){ 172 | return this.send(null) 173 | }, 174 | 175 | install(file, cb=null){ 176 | if(!file.url){ 177 | return new Promise( (resolve, reject) => reject("Can't find file URL for " + file.name) ) 178 | } 179 | 180 | return this.send({ url: file.url }) 181 | 182 | // #notstatisfying 183 | // Previous work, but not statisfying for me 184 | // keep it for further improvements or flashbacks 185 | // return this.request( 186 | // (socket) => { 187 | // socket.write(JSON.stringify({ url: file.url })) 188 | 189 | // if( typeof cb == 'function') 190 | // cb() 191 | // } 192 | // ) 193 | }, 194 | 195 | } 196 | 197 | Vue.prototype.$ps5 = ps5 198 | -------------------------------------------------------------------------------- /src/renderer/scss/pureblack.scss: -------------------------------------------------------------------------------- 1 | $bg : #000; 2 | $bg2: #111; 3 | $bg3: #1c1c1c; 4 | $bg5: #000; 5 | $text: #ccc; 6 | $text2: #bbb; 7 | $text-disabled: #333; 8 | $text-darken: #555; 9 | 10 | $border-color: #111; 11 | 12 | $menu-active-bg: #151515; 13 | 14 | $scroll-bar-track: #222; 15 | $scroll-bar-background: #111; 16 | 17 | // Scroll 18 | html.pureblack { 19 | scrollbar-color: $scroll-bar-track $scroll-bar-background; 20 | 21 | ::-webkit-scrollbar { 22 | width: 10px; /* Mostly for vertical scrollbars */ 23 | height: 5px; /* Mostly for horizontal scrollbars */ 24 | } 25 | 26 | ::-webkit-scrollbar-thumb { /* Foreground */ 27 | background: $scroll-bar-track; 28 | } 29 | ::-webkit-scrollbar-track { /* Background */ 30 | background: $scroll-bar-background; 31 | } 32 | } 33 | 34 | html.pureblack { 35 | background: $bg; 36 | color: $text; 37 | 38 | h1,h2,h3,h4,h5,h6,p { 39 | color: $text2 !important; 40 | } 41 | 42 | 43 | * { 44 | // border-color: $border-color !important; 45 | } 46 | 47 | 48 | [disabled='disabled']{ 49 | color: #555 !important; 50 | } 51 | 52 | .text-darken { 53 | color: $text-darken; 54 | } 55 | 56 | body, 57 | .el-menu, 58 | .el-table, 59 | .el-table tr, 60 | .el-table tr th, 61 | .el-table tr td { 62 | background: $bg !important; 63 | color: $text !important; 64 | } 65 | 66 | 67 | // Menu active 68 | .el-menu--horizontal>.el-menu-item.is-active, 69 | .el-menu--horizontal>.el-submenu.is-active { 70 | background: $menu-active-bg !important; 71 | color: $text !important; 72 | } 73 | 74 | .el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, 75 | .el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, 76 | .el-menu--horizontal>.el-submenu 77 | .el-submenu__title:hover 78 | { 79 | background: $menu-active-bg; 80 | color: $text; 81 | } 82 | 83 | // submenu popup 84 | .el-menu--horizontal .el-menu--popup { 85 | background: $bg !important; 86 | border: 1px solid $menu-active-bg !important; 87 | 88 | .el-menu-item { 89 | background: $bg !important; 90 | color: $text !important; 91 | 92 | &.is-active { 93 | background: $bg2 !important; 94 | color: $text; 95 | } 96 | 97 | // divider 98 | +div { 99 | background: $menu-active-bg !important; 100 | } 101 | } 102 | } 103 | 104 | // Menu underline cleanup 105 | .el-menu--horizontal>.el-menu-item, 106 | .el-menu--horizontal>.el-submenu .el-submenu__title { 107 | border-bottom: 2px solid transparent !important; 108 | color: $text; 109 | } 110 | 111 | .el-menu--horizontal { 112 | border-color: $bg3; 113 | } 114 | 115 | 116 | // Table 117 | .el-table::before { 118 | background-color: transparent; 119 | } 120 | 121 | .el-table .el-table__cell, 122 | { 123 | border-color: $border-color !important; 124 | 125 | &.is-leaf { 126 | bordr-bottom: 1px solid $border-color !important; 127 | } 128 | } 129 | 130 | // label 131 | .el-form-item__label { 132 | color: $text !important; 133 | } 134 | 135 | 136 | // Button 137 | [class^="btn-"], 138 | .el-pager li, 139 | .el-button { 140 | background: $bg !important; 141 | color: $text; 142 | cursor: pointer; 143 | border-color: $border-color !important; 144 | } 145 | 146 | // Tag 147 | .el-tag { 148 | background: $bg2; 149 | border-color: $border-color !important; 150 | } 151 | 152 | // Input 153 | .el-input__inner, 154 | .el-input-group__append, 155 | .el-input.is-disabled .el-input__inner { 156 | background: $bg2; 157 | color: $text2; 158 | border-color: $border-color !important; 159 | } 160 | 161 | // checkbox 162 | .el-checkbox__input { 163 | &:not(.is-checked) .el-checkbox__inner { 164 | background: $bg; 165 | border-color: $border-color; 166 | } 167 | 168 | &.is-disabled { 169 | .el-checkbox__inner { 170 | background: $bg3; 171 | border-color: $bg3; 172 | } 173 | 174 | +span { 175 | color: $bg3; 176 | } 177 | } 178 | } 179 | 180 | // divider 181 | .el-divider { 182 | background: $bg3; 183 | 184 | .el-divider__text { 185 | background: $bg; 186 | color: $text-disabled; 187 | } 188 | } 189 | 190 | // progress 191 | .el-progress-bar__outer { 192 | background: $bg2 !important; 193 | border: 1px solid $border-color; 194 | } 195 | 196 | // Message 197 | .el-message { 198 | background: $bg2; 199 | border-color: $bg3 !important; 200 | } 201 | 202 | // dropdown 203 | .el-popper[x-placement^=bottom] .popper_arrow, 204 | .popper__arrow { 205 | border-bottom-color: $bg3 !important; 206 | top: -7px !important; 207 | 208 | &::after { 209 | border-bottom-color: $bg !important; 210 | } 211 | } 212 | 213 | .el-dropdown-menu, 214 | .el-select-dropdown { 215 | background: $bg !important; 216 | border-color: $border-color !important; 217 | 218 | .el-dropdown-menu__item, 219 | .el-select-dropdown__item { 220 | color: $text2; 221 | &.hover, 222 | &:hover { 223 | background: $menu-active-bg !important; 224 | } 225 | 226 | &.is-disabled { 227 | color: $text-disabled !important; 228 | cursor: not-allowed; 229 | } 230 | } 231 | 232 | .popper_arrow { 233 | // border-color: transparent !important; 234 | } 235 | } 236 | 237 | // Dialog Popup 238 | .el-message-box, 239 | .el-dialog { 240 | border: 1px solid $border-color !important; 241 | border-radius: 8px !important; 242 | background: $bg; 243 | color: $text; 244 | 245 | * { 246 | color: inherit; 247 | } 248 | } 249 | 250 | // slider 251 | .el-slider__runway { 252 | background: $bg3; 253 | 254 | .el-slider__button { 255 | background: $bg2; 256 | } 257 | } 258 | 259 | } 260 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PS4 Remote PKG Sender v2 2 | [![ko-fi](https://img.shields.io/badge/Buy%20me%20a%20Shisha%20on-Ko--fi-red)](https://ko-fi.com/M4M082WK8) 3 | [![os](https://img.shields.io/badge/platform-windows%20%7C%20macos%20%7C%20linux-lightgrey)](https://github.com/Gkiokan/ps4-remote-pkg-sender) 4 | [![commits_since_release](https://img.shields.io/github/commits-since/gkiokan/ps4-remote-pkg-sender/v2.10.4)](https://github.com/Gkiokan/ps4-remote-pkg-sender/releases) 5 | [![version](https://img.shields.io/github/package-json/v/gkiokan/ps4-remote-pkg-sender)](https://github.com/Gkiokan/ps4-remote-pkg-sender/releases) 6 | [![downloads](https://img.shields.io/github/downloads/gkiokan/ps4-remote-pkg-sender/total)](https://github.com/Gkiokan/ps4-remote-pkg-sender/releases) 7 | [![last_commit](https://img.shields.io/github/last-commit/gkiokan/ps4-remote-pkg-sender)](https://github.com/Gkiokan/ps4-remote-pkg-sender) 8 | 9 | This application has been highly inspired by @irefuse and is a full refactored version of the orginal repo. 10 | 11 | Based on Electron, Webpack, Vue, Express, Axios, Element-UI and Node this does not have any extra 12 | dependencies. With this we can createa a cross platform application that works on mac, linux and Windows 13 | and provides a nice GUI. 14 | 15 | ![PS4 Package Sender Main Application Windows](https://github.com/Gkiokan/ps4-remote-pkg-sender/blob/master/screenshot.jpg) 16 | 17 | [![Full Changelog](https://img.shields.io/badge/Checkout%20-All%20Changelogs-yellow)](Changelog.md) 18 | 19 | [![Troubleshooting Guide](https://img.shields.io/badge/Checkout%20-Troubleshooting%20Guide-brightgreen)](Troubleshoot.md) 20 | 21 | ## New features in v2.* 22 | The refactored Version provides a better GUI and technicall more ordered features. 23 | - [x] Configure your server with your base path 24 | - [x] Prepare Custom Server Configuration (build-in express, apache, nginx, custom, ...) 25 | - [x] Controll the Server application by click and have it running in the background 26 | - [x] Closing windows doesn't stop the Server but stops if you quit the application. 27 | - [x] Show a list of all Server side listed PKG's 28 | - [x] Miscs download link to flatZ PS4 Remote Package Installer homebrew 29 | - [x] Extended Menu and Tray Icon 30 | - [x] Separate Server Window (Logs, Server Routes, Controls) 31 | - [x] Separate PS4 API Logs Window 32 | - [x] Catch any possible Error on Request or Response with the RPI on your PS4 33 | - [x] Scan base path (deep scan support) directory for fPKG's and serve them with the server 34 | - [x] Search for titles through your found files 35 | - [x] Add Served files to your Queue and install them on your PS4 36 | - [x] Processing Center reflects any status changes in your server list, too 37 | - [x] Set custom timeout and update interval with a slider 38 | - [x] Added Seperate Changelog file and Troubleshooting Guide 39 | - [x] Add a FAQ and Troubleshooting Area 40 | - [x] Configuration values for timeout and heartbeat 41 | - [x] Implement HB Store and direct install fPKG's 42 | - [x] Queue Scanner (start next file in the queue after one is finished) 43 | - [x] Scroll To Top 44 | - [x] Auto update checker for latest release version 45 | - [x] Bulk Action for adding to Queue 46 | 47 | ## ToDo's for the future (Comming into v2.10+) 48 | - [x] Drag & Drop files and folders 49 | - [x] RPSV2 API Service for dynamic configurations 50 | - [x] Add PS5 Support (v2.10.4) 51 | - [x] Add Queue Scanner Bulk Request Support for PS5 (v2.10.4) 52 | - [x] SFO Header Reader Implementation (WIP v2.11+) 53 | - [x] Preview Game title covers (WIP v2.11+) 54 | - [x] Read CUSA from file hex values instead of title (WIP v2.11+) 55 | - [x] Install via GoldHEN embedded RPI (WIP v2.11+) 56 | - [ ] Context Menu on Processing Center 57 | - [ ] Group PKG's by TitleID and Type (Base, Update, DLC) 58 | - [ ] One click to install all Group based PKG (send all PKGs from TitleID X) 59 | - [ ] Add Translations though RPSV2 API 60 | - [ ] Search your PS4 automatically in the current network 61 | - [ ] Implement Auto-updater 62 | - [ ] Import / Export Configuration 63 | - [ ] Serve as global Server Host and provide Server Files over Internet 64 | - [ ] Show files from Hosts / Users 65 | - [ ] Save PS4 (local, wlan, wan, internet) and make them chooseable 66 | - [ ] Chrome Extension for external usage 67 | - [ ] GoldHen Cheats Manager 68 | - [ ] Integrated FTP Client 69 | 70 | ## How To 71 | So there you have it. How can you use it? 72 | 73 | ### on PS4 74 | 1.) Start HEN v1.8+ / latest GoldHEN 75 | 2.) Start flatZ Remote Package Installer 76 | 77 | ### on PC 78 | 1.) Start PS4 Package Sender V2 79 | 2.) Switch to Config and select your Networkinterface (IP Adress) 80 | 2.1) Choose a Server Port if necessary and apply or restart the server 81 | 3.) Choose your base path where your files are settled 82 | 4.) Switch to Server and add your files to the queue (Processing Center) 83 | 5.) Start your install process with any of your files. 84 | 85 | ## Troubleshooting 86 | Your PS4 and PC have to be on the same Network. 87 | If you have connection issues, check your Router or Firewall. 88 | If you get timeout, RPI is not running on your PS4 or PS4 IP Adress is wrong or timeout to low. 89 | If you get Playstation not available error, check RPI on PS4 and restart it. 90 | If you get the White Screen of Dead, use the unpacked version. 91 | If you think there is an issue, please report it. 92 | [Checkout the full Troubleshooting Guide](Troubleshoot.md) 93 | 94 | ## Credits 95 | Thanks to flatz, Specter, xvortex, and all initial developers for their efford on the scene. 96 | Thanks to my primary testers CyB1K, mtnjustme, Masamune3210, hypermist. 97 | Thanks to Sam Daniel for the first full fledged Tutorial on windows and mac. 98 | Thanks to the awesome community and everyone else on their feedback that I am missing here. 99 | Mega thanks to my wife having patience while I develop this awesome tool. 100 | 101 | ## Support 102 | If you want to Support me and my development, you can do this here. 103 | That high quality application development is done in my free time and takes much effort. 104 | There are so many nice features that are on my todo (black)-list that are not done yet. 105 | I'd like to invest more time into this Tool and provide you one of the best Tools ever. 106 | 107 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/M4M082WK8) 108 | 109 | ## Disclaimer 110 | This tool is a Proof of Concept (PoC) on my skills and also part of educational process. 111 | The PS4 name and PS Logo are trademarks of their respective owners. 112 | Exclusive approval request to Sony has been made. Until then the fair-use rule comes into effect. 113 | If there is any law issue, please report that to me directly, I will take immediately action. 114 | 115 | Made with passion and love. :heart: 116 | -------------------------------------------------------------------------------- /src/renderer/plugins/ps4_goldhen.js: -------------------------------------------------------------------------------- 1 | /* 2 | GoldHEN embedded RPI 3 | 4 | Send install requests 5 | curl --data '{"url":"url"} 'http://:9090' 6 | 7 | params.id = CONTENT_ID 8 | params.contentUrl = URL 9 | params.iconPath = icon0 path 10 | params.contentName = name 11 | */ 12 | 13 | import Vue from 'vue' 14 | import store from '../store' 15 | import { connect } from 'http2' 16 | // import net from 'net' 17 | const net = require('net') 18 | 19 | let ps4_goldhen = { 20 | 21 | debug(){ 22 | let ps4ip = store.getters['app/getPS4IP'] 23 | console.log("Check PS IP " + ps4ip) 24 | }, 25 | 26 | // #todo refactor to ps5 27 | getURL(of='ps'){ 28 | if(of == 'url'){ 29 | return store.getters['app/getPS4IP'] 30 | } 31 | 32 | if(of == 'ps'){ 33 | let url = store.getters['app/getPS4IP'] 34 | let parts = url.split(':') 35 | return { host: parts[0], port: parts[1] } 36 | } 37 | 38 | if(of == 'server') 39 | return store.getters['app/getServerIP'] 40 | 41 | return '' 42 | }, 43 | 44 | getTimeout(min=2000){ 45 | let timeout = store.getters['app/getPS4Timeout'] 46 | return timeout < min ? min : timeout 47 | }, 48 | 49 | request( onSuccess=null, onResponse=null ){ 50 | return new Promise( (resolve, reject) => { 51 | // Set up a timeout function 52 | const handleTimeout = () => { 53 | console.error("PS4 API connection timed out"); 54 | reject("PS4 Connection timed out. PS4 not available on " + this.getURL('url') ) 55 | socket.destroy() 56 | }; 57 | 58 | // Start the timeout timer 59 | let connectionTimeout = setTimeout(handleTimeout, this.getTimeout()); 60 | 61 | // console.log("PS5 Connection to ", connectTo) 62 | const socket = new net.Socket() 63 | 64 | // Connect to the Server 65 | socket.connect(this.getURL('ps'), () => { 66 | clearTimeout(connectionTimeout) 67 | 68 | if( typeof onSuccess == 'function' ){ 69 | onSuccess(socket) 70 | } 71 | else { 72 | resolve(true) 73 | } 74 | }) 75 | 76 | // Listen for data from the Server 77 | socket.on('data', (r) => { 78 | let response = r.toString() 79 | let json = null 80 | 81 | try { 82 | console.log(json) 83 | json = JSON.parse(r) 84 | resolve(json, response) 85 | } 86 | catch(jsonError) { 87 | reject(jsonError) 88 | } 89 | }) 90 | 91 | // Handle errors 92 | socket.on('error', (err) => { 93 | clearTimeout(connectionTimeout) 94 | reject(err) 95 | }) 96 | }) 97 | }, 98 | 99 | send(requestObject={}){ 100 | return new Promise((resolve, reject) => { 101 | const client = new net.Socket(); 102 | 103 | // Set up a timeout function 104 | const timeoutId = setTimeout(() => { 105 | client.destroy() 106 | reject( new Error('PS4 Connection timed out at ' + this.getURL('url') ) ) 107 | }, this.getTimeout()); 108 | 109 | // Connect to the server 110 | client.connect(this.getURL('ps'), () => { 111 | console.log('Connected to PS4'); 112 | 113 | if( !requestObject ){ 114 | client.destroy() 115 | resolve(true) 116 | return 117 | } 118 | 119 | const jsonString = JSON.stringify(requestObject); 120 | client.write(jsonString); 121 | }); 122 | 123 | // Listen for data from the server 124 | client.on('data', (data) => { 125 | clearTimeout(timeoutId); 126 | console.log('Received data from server:', data.toString()); 127 | 128 | try { 129 | const receivedObject = JSON.parse(data); 130 | resolve(receivedObject); 131 | client.end(); 132 | } 133 | catch(e){ 134 | reject(e) 135 | } 136 | }); 137 | 138 | // Handle errors 139 | client.on('error', (error) => { 140 | clearTimeout(timeoutId); 141 | console.error('Socket error:', error); 142 | 143 | if( error.code == 'ECONNREFUSED' ) 144 | return reject(`PS4 Connection failed at ${this.getURL('url')}`) 145 | 146 | reject(error); 147 | }); 148 | 149 | // Handle socket closure 150 | client.on('close', () => { 151 | console.log("Close Connection to PS4") 152 | clearTimeout(timeoutId); 153 | }) 154 | }) 155 | }, 156 | 157 | getErrorCodeMessage(code=''){ 158 | let message = code 159 | 160 | if(code==2157510681) 161 | message = code + " | task doesn't exist (?)" 162 | 163 | if(code==2157510663) 164 | message = code + " | already installed (?)" 165 | 166 | if(code==2157510677) 167 | message = code + " | It seems to be installed already" 168 | 169 | if(code==2157510789) 170 | message = code + " | Not enough storage" 171 | 172 | return message 173 | }, 174 | 175 | checkPS4(){ 176 | return this.send(null) 177 | }, 178 | 179 | install(file, cb=null){ 180 | if(!file.url){ 181 | return new Promise( (resolve, reject) => reject("Can't find file URL for " + file.name) ) 182 | } 183 | 184 | let o = { 185 | id: file.sfo.CONTENT_ID, 186 | contentUrl: file.url, 187 | iconPath: file.image, 188 | contentName: file.sfo.TITLE, 189 | } 190 | 191 | console.log("Prepare Send Object", o) 192 | 193 | return this.send(o) 194 | 195 | // #notstatisfying 196 | // Previous work, but not statisfying for me 197 | // keep it for further improvements or flashbacks 198 | // return this.request( 199 | // (socket) => { 200 | // socket.write(JSON.stringify({ url: file.url })) 201 | 202 | // if( typeof cb == 'function') 203 | // cb() 204 | // } 205 | // ) 206 | }, 207 | 208 | } 209 | 210 | Vue.prototype.$ps4_goldhen = ps4_goldhen 211 | -------------------------------------------------------------------------------- /Troubleshoot.md: -------------------------------------------------------------------------------- 1 | # PS4 Remote PKG Sender v2 - Troubleshooting 2 | 3 | 4 | ## I have a White Screen of Death 5 | This happens in general with the portable Version on Windows. 6 | Please try to use the unpacked version. That works for sure and has been tested. 7 | This issue is fixed in v2.4.3. If you still get an error, please report. 8 | 9 | ## I can't run the app because macOS blocks the app 10 | Because I haven't signed my app yet with a paid apple certificate, that happens. 11 | It is normal for the first time that you try to open it for it to be blocked. 12 | Go to System Settings `Preferences > Security > General` 13 | There should be the app listed as blocked. You can explicity allow it then to run. 14 | Don't worry. My app is clean and open source. Once I get money for the certificate I will fix that. 15 | 16 | ## I can't run the app because windows blocks the app 17 | Even windows need a paid certificate to have it signed, its the same circumstance as on mac. 18 | Extend the window and allow the app to be run. 19 | Again. Don't worry. My app is clean. Once I get money for the certificate I will fix that. 20 | 21 | ## Playstation not available 22 | The app can not connect to RPI on PS4. 23 | Check your Firewall and see if something is blocking your connection from your PC to PS4. 24 | Ping your PS4 in your Terminal / Commandline with `ping PS4_IP_ADRESS`. 25 | Restart RPI (Remote Package Installer) and stay on splash screen. 26 | 27 | ## I get Timeout error 28 | Timeout can mean any of the following errors but in general it means RPI cannot be connected to. 29 | 30 | ##### Option a) 31 | Try to restart the RPI on your PS4. Sometimes RPI changes into a kind of suspend mode and has no reaction. 32 | Restarting RPI helps in most cases. 33 | 34 | ##### Option b) 35 | Timeout on Request because timeout value is to short. 36 | Set a higher request timeout value before the Sender kills the request when RPI takes to long to respond. 37 | Mostly seen on the Install Request because RPI needs to prepare some stuff before it sends a valid Task ID back. 38 | 39 | ##### Option c) 40 | Sometimes a restart just fixes connection issues, especially on windows. 41 | Restart your PC and PS4, try again. Some users reported back that this helped. 42 | 43 | ##### Option d) 44 | There is something weird with the latest update of GoldHen. 45 | I am not sure yet but I can not debug this as I am still on 5.05. 46 | 47 | Check the PS4 API Logs and see if you get any response. 48 | If the response for Check PS4 works, but you don't proceed on the install request 49 | then you might be affected by this bug in GoldHen. 50 | 51 | I assume that because there was a update and since then some users reported install issues. 52 | Try another HEN edition as this was initially working from HENv1.8 until GoldHen 2.0 for sure. 53 | If nothing helps, try another PKG Sender - I guess they will not work eithier. 54 | 55 | A working GoldHen 2.0 (old version) can be found on e.g. nightkinghost. 56 | 57 | ##### Option e) 58 | You can try another Installation type. Try using IPI and FTP until my app does support 59 | it natively or the RPIOOSDK. Support for both target apps will done soon. 60 | 61 | 62 | ## I get a long Error Code 63 | This will be patched in the v2.4.2 but for the completeness sake here they are 64 | Those Error Codes are not documented yet but I've patched the known ones which are: 65 | `2157510681` Task doesn't exist -> RPI can't find any task associated with the id 66 | `2157510663` already installed -> the app you are trying to install is already installed, delete the copy on your PS4 first and try again 67 | `2157510677` App seems to be installed already (duplicate?) -> Delete the app and any app chunks that are on your PS4 first and try again 68 | `2157510789` Not enough storage 69 | `2157510920` Not known yet 70 | `2143223530` Ethernet/WiFi Connection Issue 71 | 72 | 73 | 74 | ## Unable to set up prerequisites for package 75 | Thanks to marcussacana we could debug this error down. 76 | This error will be sent from the Remote Package Installer when there is 77 | a HTTP Protocol error on the serving point. In case of the RPS v2, if it occurs, just restart the server on another Port. 78 | 79 | If you get the error message from any other package sender, this is due 80 | the lack of missing multipart stream support. 81 | 82 | ## My RPI crashes on console 83 | This happend to a couple of users when we try to hit RPI too hard. 84 | RPI cann't handle too many concurrent requests and crashes after a while 85 | especially when you try to install something big 100GB+ PKG files or 86 | installing multiple files at once and have a low update interval value. 87 | 88 | It may help if you leave the interval value arround 2-3 secs because 89 | updating progress info is also a request that will be sent upon the interval. 90 | 91 | In v2.4.2 you can still send as many install request as you want, that is not limited yet, 92 | but consider the fact that once the installation starts, RPI has nothing more to do with it. 93 | Download still continues on PS4 even if you jump out of RPI! Keep that in mind! 94 | 95 | ## Server not starting 96 | If you see that your server is on error, please check the Server Window for the logs. 97 | It will give you a specific error message what happend. Mostly it is a blocked port. 98 | Just change the Port and press apply, refresh or just i/o button to close and start the server. 99 | 100 | ## Can't see HB-Store Tab 101 | You have to activate that feature first on Settings below the feature list. 102 | 103 | ## Can't see HB-Store Items 104 | Due the new security policy on pkg-zone.com you need to use the latest version greater then v2.9. 105 | 106 | ## Legacy or Refactored HB-Store Mode? 107 | Legacy Mode is still there if you have a custom server with the "old cdn host". 108 | Refactored Mode (recommended) implements the new Store Site API and have multiple 109 | benefits besides filtering by category and search value. 110 | 111 | ## I lost the main Store API CDN 112 | Put in `http://api.pkg-zone.com/` (must end with / at the end) 113 | 114 | ## Can't Install HB-Store items directly 115 | Until the refactored version of the HB-Store goes public you can download the App, 116 | put it on your base_path folder and install it the normal way. 117 | 118 | ## Can't download items from HB-Store 119 | If the title says "Just a moment", double click it and expand to see the full view. 120 | Due the new security policy on pkg-zone.com you need to manually verify that you are not a bot. 121 | 122 | ## Application doesn't close 123 | Closing windows doesn't kill the App. 124 | This is intented to not accidently kill the server. 125 | If you want to close the application fully you have to go 126 | through the Menu `Application > Quit` or just click the power button on the top right. 127 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | import { app, session, BrowserWindow, Notification, ipcMain, globalShortcut, protocol } from 'electron' 2 | import path from 'path' 3 | import { format as formatUrl } from 'url' 4 | 5 | import helper from './helper' 6 | import menu from './menu' 7 | import tray from './tray' 8 | import store from './../renderer/store/index.js' 9 | 10 | // import './crashReporter' 11 | 12 | // prepatch 13 | console.log("Plattform Check " + process.platform) 14 | if (process.platform === "linux") { 15 | console.log("Apply --no-sandbox to commandline to fix Linux (debian) graphical issues") 16 | console.log("More Info: https://github.com/Gkiokan/ps4-remote-pkg-sender/issues/76#issuecomment-2127757683") 17 | app.commandLine.appendSwitch("no-sandbox"); 18 | } 19 | 20 | // set vars 21 | const isDevelopment = process.env.NODE_ENV !== 'production' 22 | 23 | const showServerWindowOnStartUp = false 24 | const showServerDevtools = false 25 | const showPS4DevTools = false 26 | const showMainDevTools = isDevelopment 27 | 28 | // global reference to mainWindow (necessary to prevent window from being garbage collected) 29 | let windows = { 30 | info: null, 31 | main: null, 32 | server: null, 33 | ps4: null, 34 | } 35 | 36 | // Create Windows 37 | function createMainWindow() { 38 | const window = helper.createWindowInstance('/', { 39 | width: 1300, height: 800, frame: false, 40 | }, showMainDevTools) 41 | 42 | window.on('close', (event) => { 43 | event.preventDefault() 44 | window.hide() 45 | }) 46 | window.on('closed', () => { windows.main = null }) 47 | // window.webContents.openDevTools() 48 | 49 | // handle download child windows and autoclose 50 | helper.autocloseAfterDownload(window) 51 | 52 | windows.main = window 53 | 54 | // for hard debugging 55 | // mainWindow.webContents.openDevTools() 56 | } 57 | 58 | // create Server Window 59 | function createServerWindow(){ 60 | const window = helper.createWindowInstance('/app/Server', { 61 | width: 800, height: 500, title: 'Server', show: showServerWindowOnStartUp, 62 | }, showServerDevtools) 63 | window.on('close', (event) => { 64 | event.preventDefault() 65 | window.hide() 66 | }) 67 | window.on('closed', (event) => { windows.server = null }) 68 | windows.server = window 69 | } 70 | 71 | // create Info Window 72 | function createInfoWindow(){ 73 | const window = helper.createWindowInstance('/info', { 74 | width: 500, height: 600, title: 'Info', show: false, 75 | }, false) 76 | window.on('close', (event) => { 77 | event.preventDefault() 78 | window.hide() 79 | }) 80 | window.on('closed', (event) => { windows.info = null }) 81 | windows.info = window 82 | } 83 | 84 | // create Info Window 85 | function createPS4Window(){ 86 | const window = helper.createWindowInstance('/ps4', { 87 | width: 800, height: 800, title: 'PS4', show: false, 88 | }, showPS4DevTools) 89 | window.on('close', (event) => { 90 | event.preventDefault() 91 | window.hide() 92 | }) 93 | window.on('closed', (event) => { windows.ps4 = null }) 94 | windows.ps4 = window 95 | } 96 | 97 | // hearthbeat 98 | function hearthbeat(){ 99 | setInterval( () => { 100 | store.dispatch('app/addTime') 101 | }, 1000) 102 | } 103 | 104 | // registerChannel 105 | function registerChannel(){ 106 | ipcMain.on('server', (event, data) => windows.server.webContents.send('server', data) ) 107 | ipcMain.on('server-show', () => windows.server.show() ) 108 | ipcMain.on('show', (event, data) => showWindow(data) ) 109 | 110 | ipcMain.on('main', (event, data) => windows.main.webContents.send('main', data) ) 111 | ipcMain.on('main-error', (event, data) => windows.main.webContents.send('main-error', data) ) 112 | ipcMain.on('main-route', (event, data) => windows.main.webContents.send('main-route', data) ) 113 | 114 | ipcMain.on('ps4', (event, data) => windows.ps4.webContents.send('ps4', data) ) 115 | 116 | ipcMain.on('error', (event, data) => windows.main.webContents.send('error', data) ) 117 | ipcMain.on('notify', (event, data) => notify(data) ) 118 | ipcMain.on('quit', () => app.quit() ) 119 | } 120 | 121 | // add Shortcuts 122 | function addShortcuts(){ 123 | globalShortcut.register('CommandOrControl+C', () => { 124 | contents.copy() 125 | }) 126 | 127 | globalShortcut.register('CommandOrControl+V', () => { 128 | contents.paste() 129 | }) 130 | } 131 | 132 | // create Protocols 133 | function createProtocols(){ 134 | return; 135 | protocol.registerSchemesAsPrivileged([ 136 | { scheme: 'http', privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } }, 137 | { scheme: 'https', privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } }, 138 | { scheme: 'mailto', privileges: { standard: true } }, 139 | ]); 140 | } 141 | 142 | // notifications 143 | function notify(data){ 144 | new Notification(data).show() 145 | } 146 | 147 | // show window 148 | function showWindow(data){ 149 | if(data == 'ps4') 150 | windows.ps4.show() 151 | 152 | if(data == 'server') 153 | windows.server.show() 154 | 155 | if(data == 'info') 156 | windows.info.show() 157 | } 158 | 159 | // quit application when all windows are closed 160 | app.on('window-all-closed', () => { 161 | console.log("All windows are closed. Kill all processes.") 162 | // on macOS it is common for applications to stay open until the user explicitly quits 163 | // if (process.platform !== 'darwin') { 164 | // app.quit() 165 | // } 166 | 167 | app.quit() 168 | }) 169 | 170 | // fix quit issue when open windows are left 171 | app.on('before-quit', (event) => { 172 | console.log("Closing applications") 173 | 174 | console.log("Closing Server") 175 | windows.server.webContents.send('server', 'stop') 176 | 177 | setTimeout(() => { 178 | Object.values(windows).map( (win) => { 179 | if(!win){ 180 | return console.log("No win object") 181 | } 182 | 183 | win.removeAllListeners('close') 184 | win.close() 185 | }) 186 | }, 500) 187 | 188 | console.log("Application closed.") 189 | }) 190 | 191 | // activate hook 192 | app.on('activate', () => { 193 | windows.main.show() 194 | }) 195 | 196 | // create main BrowserWindow when electron is ready 197 | app.on('ready', () => { 198 | createMainWindow() 199 | createServerWindow() 200 | createInfoWindow() 201 | createPS4Window() 202 | createProtocols() 203 | 204 | // addShortcuts() 205 | 206 | menu.createMenu() 207 | tray.createTray() 208 | 209 | new Notification({ title: 'PS4 Remote PKG Sender', body: 'Welcome to PS4 Remote PKG Installer. \nStart your Remote Package Installer App on your PS4 and add your PKG files here. \nHave fun.' }).show() 210 | registerChannel() 211 | // hearthbeat() 212 | }) 213 | 214 | 215 | export default windows 216 | -------------------------------------------------------------------------------- /src/renderer/pages/User.vue: -------------------------------------------------------------------------------- 1 | 116 | 117 | 137 | 138 | 140 | -------------------------------------------------------------------------------- /src/renderer/components/PS4Config.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 173 | 174 | 176 | -------------------------------------------------------------------------------- /src/renderer/scss/darkmode.scss: -------------------------------------------------------------------------------- 1 | $bg : #0d0d0d; 2 | $bg2: #1a1a1a; 3 | $bg3: #333; 4 | $bg5: #555; 5 | $text: #ccc; 6 | $text2: #bbb; 7 | $text-disabled: #333; 8 | $text-darken: #555; 9 | 10 | $border-color: #333; 11 | 12 | $menu-active-bg: $bg2; 13 | 14 | $scroll-bar-track: #222; 15 | $scroll-bar-background: #111; 16 | 17 | // Scroll 18 | html.dark { 19 | scrollbar-color: $scroll-bar-track $scroll-bar-background; 20 | 21 | ::-webkit-scrollbar { 22 | width: 10px; /* Mostly for vertical scrollbars */ 23 | height: 5px; /* Mostly for horizontal scrollbars */ 24 | } 25 | 26 | ::-webkit-scrollbar-thumb { /* Foreground */ 27 | background: $scroll-bar-track; 28 | } 29 | ::-webkit-scrollbar-track { /* Background */ 30 | background: $scroll-bar-background; 31 | } 32 | } 33 | 34 | html.dark { 35 | background: $bg; 36 | color: $text; 37 | 38 | h1,h2,h3,h4,h5,h6,p { 39 | color: $text2 !important; 40 | } 41 | 42 | 43 | * { 44 | // border-color: $border-color !important; 45 | } 46 | 47 | 48 | [disabled='disabled']{ 49 | color: #555 !important; 50 | } 51 | 52 | .text-darken { 53 | color: $text-darken; 54 | } 55 | 56 | body, 57 | .el-menu, 58 | .el-table, 59 | .el-table tr, 60 | .el-table tr th, 61 | .el-table tr td { 62 | background: $bg !important; 63 | color: $text !important; 64 | } 65 | 66 | 67 | // Menu active 68 | .el-menu--horizontal>.el-menu-item.is-active, 69 | .el-menu--horizontal>.el-submenu.is-active { 70 | background: $menu-active-bg !important; 71 | color: $text !important; 72 | } 73 | 74 | .el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, 75 | .el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, 76 | .el-menu--horizontal>.el-submenu 77 | .el-submenu__title:hover 78 | { 79 | background: $menu-active-bg; 80 | color: $text; 81 | } 82 | 83 | // submenu popup 84 | .el-menu--horizontal .el-menu--popup { 85 | background: $bg !important; 86 | border: 1px solid $menu-active-bg !important; 87 | 88 | .el-menu-item { 89 | background: $bg !important; 90 | color: $text !important; 91 | 92 | &.is-active { 93 | background: $bg2 !important; 94 | color: $text; 95 | } 96 | 97 | // divider 98 | +div { 99 | background: $menu-active-bg !important; 100 | } 101 | } 102 | } 103 | 104 | // Menu underline cleanup 105 | .el-menu--horizontal>.el-menu-item, 106 | .el-menu--horizontal>.el-submenu .el-submenu__title { 107 | border-bottom: 2px solid transparent !important; 108 | color: $text; 109 | } 110 | 111 | .el-menu--horizontal { 112 | border-color: $bg3; 113 | } 114 | 115 | 116 | // Table 117 | .el-table::before { 118 | background-color: transparent; 119 | } 120 | 121 | .el-table .el-table__cell 122 | { 123 | border-color: $border-color !important; 124 | 125 | &.is-leaf { 126 | border-bottom: 1px solid $border-color !important; 127 | } 128 | } 129 | 130 | // label 131 | .el-form-item__label { 132 | color: $text !important; 133 | } 134 | 135 | 136 | // Button 137 | [class^="btn-"], 138 | .el-pager li, 139 | .el-button { 140 | background: $bg !important; 141 | color: $text; 142 | cursor: pointer; 143 | border-color: $border-color !important; 144 | } 145 | 146 | // Tag 147 | .el-tag { 148 | background: $bg2; 149 | border-color: $border-color !important; 150 | } 151 | 152 | // Input 153 | .el-input__inner, 154 | .el-input-group__append, 155 | .el-input.is-disabled .el-input__inner { 156 | background: $bg2; 157 | color: $text2; 158 | border-color: $border-color !important; 159 | } 160 | 161 | // checkbox 162 | .el-checkbox__input { 163 | &:not(.is-checked) .el-checkbox__inner { 164 | background: $bg; 165 | border-color: $border-color; 166 | } 167 | 168 | &.is-disabled { 169 | .el-checkbox__inner { 170 | background: $bg3; 171 | border-color: $bg3; 172 | } 173 | 174 | +span { 175 | color: $bg3; 176 | } 177 | } 178 | } 179 | 180 | // divider 181 | .el-divider { 182 | background: $bg3; 183 | 184 | .el-divider__text { 185 | background: $bg; 186 | color: $bg5; 187 | } 188 | } 189 | 190 | // progress 191 | .el-progress-bar__outer { 192 | background: $bg2 !important; 193 | border: 1px solid $border-color; 194 | } 195 | 196 | // Message 197 | .el-message { 198 | background: $bg2; 199 | border-color: $bg3 !important; 200 | } 201 | 202 | // dropdown 203 | .el-popper[x-placement^=bottom] .popper_arrow, 204 | .popper__arrow { 205 | border-bottom-color: $bg3 !important; 206 | top: -7px !important; 207 | 208 | &::after { 209 | border-bottom-color: $bg !important; 210 | } 211 | } 212 | 213 | .el-dropdown-menu, 214 | .el-select-dropdown { 215 | background: $bg !important; 216 | border-color: $border-color !important; 217 | 218 | .el-dropdown-menu__item, 219 | .el-select-dropdown__item { 220 | color: $text2; 221 | &.hover, 222 | &:hover { 223 | background: $menu-active-bg !important; 224 | } 225 | 226 | &.is-disabled { 227 | color: $text-disabled !important; 228 | cursor: not-allowed; 229 | } 230 | } 231 | 232 | // .popper_arrow { 233 | // // border-color: transparent !important; 234 | // } 235 | } 236 | 237 | // Dialog Popup 238 | .el-message-box, 239 | .el-dialog { 240 | border: 1px solid $border-color !important; 241 | border-radius: 8px !important; 242 | background: $bg; 243 | color: $text; 244 | 245 | * { 246 | color: inherit; 247 | } 248 | } 249 | 250 | // slider 251 | .el-slider__runway { 252 | background: $bg3; 253 | 254 | .el-slider__button { 255 | background: $bg2; 256 | } 257 | } 258 | 259 | } 260 | 261 | 262 | // darkmode fix 263 | html.dark { 264 | .top { 265 | .el-button--info { 266 | background-color: #333 !important; 267 | // border-color: #909399 !important; 268 | } 269 | } 270 | } 271 | 272 | // pureblack fix 273 | html.pureblack { 274 | .top { 275 | .el-button--info { 276 | background-color: #1a1a1a !important; 277 | // border-color: #909399 !important; 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/components/ServerConfig.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 213 | 214 | 219 | --------------------------------------------------------------------------------