├── assets
├── .gitkeep
└── js
│ ├── prompt.js
│ ├── pluginWindowPreload.js
│ ├── inject.js
│ ├── loadModule.js
│ ├── autoLogin.js
│ ├── gbf
│ └── fixSlideBar.js
│ ├── gif.js
│ ├── pluginHelper.js
│ ├── aigisAnimate.js
│ └── fixPage.js
├── src
├── assets
│ ├── .gitkeep
│ └── i18n
│ │ ├── jp.json
│ │ ├── en.json
│ │ └── cn.json
├── favicon2.ico
├── app
│ ├── uiFramework
│ │ ├── modules
│ │ │ ├── statusbar
│ │ │ │ ├── toolbar
│ │ │ │ │ ├── modules
│ │ │ │ │ │ ├── setting
│ │ │ │ │ │ │ ├── modules
│ │ │ │ │ │ │ │ └── plugins
│ │ │ │ │ │ │ │ │ ├── plugins.component.scss
│ │ │ │ │ │ │ │ │ ├── plugins.component.html
│ │ │ │ │ │ │ │ │ ├── plugins.modules.ts
│ │ │ │ │ │ │ │ │ ├── plugins.component.ts
│ │ │ │ │ │ │ │ │ └── components
│ │ │ │ │ │ │ │ │ ├── plugin.component.html
│ │ │ │ │ │ │ │ │ ├── plugin.component.ts
│ │ │ │ │ │ │ │ │ └── plugin.component.scss
│ │ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ │ ├── util
│ │ │ │ │ │ │ │ │ ├── util.component.scss
│ │ │ │ │ │ │ │ │ ├── util.component.html
│ │ │ │ │ │ │ │ │ └── util.component.ts
│ │ │ │ │ │ │ │ ├── proxy
│ │ │ │ │ │ │ │ │ ├── proxy.component.scss
│ │ │ │ │ │ │ │ │ ├── proxy.component.html
│ │ │ │ │ │ │ │ │ └── proxy.component.ts
│ │ │ │ │ │ │ │ ├── map
│ │ │ │ │ │ │ │ │ ├── map.component.scss
│ │ │ │ │ │ │ │ │ ├── map.component.ts
│ │ │ │ │ │ │ │ │ └── map.component.html
│ │ │ │ │ │ │ │ └── account
│ │ │ │ │ │ │ │ │ ├── account.component.scss
│ │ │ │ │ │ │ │ │ ├── account.component.ts
│ │ │ │ │ │ │ │ │ └── account.component.html
│ │ │ │ │ │ │ ├── setting.component.scss
│ │ │ │ │ │ │ ├── setting.component.ts
│ │ │ │ │ │ │ ├── setting.module.ts
│ │ │ │ │ │ │ └── setting.component.html
│ │ │ │ │ │ └── plugin
│ │ │ │ │ │ │ ├── webview.directive.ts
│ │ │ │ │ │ │ ├── plugin.component.html
│ │ │ │ │ │ │ ├── plugin.module.ts
│ │ │ │ │ │ │ ├── plugin.component.scss
│ │ │ │ │ │ │ └── plugin.component.ts
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── gameselect
│ │ │ │ │ │ │ ├── gameselect.component.html
│ │ │ │ │ │ │ ├── gameselect.component.scss
│ │ │ │ │ │ │ └── gameselect.component.ts
│ │ │ │ │ │ └── accountselect
│ │ │ │ │ │ │ ├── accountselect.component.scss
│ │ │ │ │ │ │ ├── accountselect.component.html
│ │ │ │ │ │ │ └── accountselect.component.ts
│ │ │ │ │ ├── toolbar.component.scss
│ │ │ │ │ ├── toolbar.module.ts
│ │ │ │ │ ├── toolbar.component.ts
│ │ │ │ │ └── toolbar.component.html
│ │ │ │ ├── statusbar.module.ts
│ │ │ │ ├── statusbar.component.html
│ │ │ │ ├── statusbar.component.ts
│ │ │ │ └── statusbar.component.scss
│ │ │ └── game
│ │ │ │ ├── game.component.scss
│ │ │ │ ├── webview.directive.ts
│ │ │ │ ├── game.module.ts
│ │ │ │ ├── game.component.html
│ │ │ │ └── game.component.ts
│ │ ├── uiframework.component.scss
│ │ ├── webview.directive.ts
│ │ ├── components
│ │ │ ├── navbar
│ │ │ │ ├── navbar.component.html
│ │ │ │ ├── navbar.component.ts
│ │ │ │ └── navbar.component.scss
│ │ │ ├── dialog
│ │ │ │ ├── dialog.component.html
│ │ │ │ ├── dialog.component.ts
│ │ │ │ ├── dialog.component.spec.ts
│ │ │ │ └── dialog.component.scss
│ │ │ └── main
│ │ │ │ ├── main.component.scss
│ │ │ │ ├── main.component.html
│ │ │ │ └── main.component.ts
│ │ ├── uiframework.component.html
│ │ ├── uiframework.module.ts
│ │ └── uiframework.component.ts
│ ├── app.component.html
│ ├── fa
│ │ └── fonts
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ ├── res-manager
│ │ ├── al.service.ts
│ │ └── res-manager.module.ts
│ ├── core
│ │ ├── languageList.ts
│ │ ├── game.model.ts
│ │ ├── keyMapper.ts
│ │ ├── hotkey.service.ts
│ │ ├── core.module.ts
│ │ ├── log.service.ts
│ │ ├── util.ts
│ │ ├── store.ts
│ │ ├── electron.service.ts
│ │ └── pluginHelper.ts
│ ├── global
│ │ ├── config.ts
│ │ ├── global.module.ts
│ │ ├── globalStatus.service.ts
│ │ └── globalSetting.service.ts
│ ├── app.component.scss
│ ├── webview.directive.ts
│ ├── app-routing.module.ts
│ ├── shared.module.ts
│ ├── gameData
│ │ ├── gamedata.module.ts
│ │ ├── aigis
│ │ │ ├── util.ts
│ │ │ ├── EventList.ts
│ │ │ ├── decode.ts
│ │ │ ├── statistics.service.ts
│ │ │ ├── statistics
│ │ │ │ └── parseQuestName.ts
│ │ │ ├── xml2json.ts
│ │ │ └── aigis.service.ts
│ │ └── debugger.service.ts
│ ├── app.component.ts
│ └── app.module.ts
├── environments
│ ├── environment.ts
│ ├── environment.prod.ts
│ └── environment.dev.ts
├── typings.d.ts
├── tsconfig.app.json
├── index.html
├── main.ts
├── tsconfig.spec.json
├── styles.scss
├── test.ts
└── polyfills.ts
├── .npmrc
├── postcss.config.js
├── _config.yml
├── .vs
├── ProjectSettings.json
├── slnx.sqlite
├── AigisPlayer
│ └── v15
│ │ └── .suo
└── VSWorkspaceState.json
├── icon.png
├── main.ico
├── globalConfig.conf
├── e2e
├── app.po.ts
├── tsconfig.e2e.json
└── app.e2e-spec.ts
├── .prettierrc
├── appveyor.yml
├── .editorconfig
├── .travis.yml
├── tsconfig-serve.json
├── tsconfig.json
├── tsconfig-build.json
├── postinstall.js
├── angular.webpack.js
├── .gitignore
├── protractor.conf.js
├── electron-builder.json
├── karma.conf.js
├── LICENSE
├── tslint.json
├── .github
└── workflows
│ └── main.yml
├── proxyServer.ts
├── package.json
├── README.md
├── angular.json
└── requestHandler.ts
/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-architect
--------------------------------------------------------------------------------
/.vs/ProjectSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "CurrentProjectSetting": null
3 | }
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/icon.png
--------------------------------------------------------------------------------
/main.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/main.ico
--------------------------------------------------------------------------------
/.vs/slnx.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/.vs/slnx.sqlite
--------------------------------------------------------------------------------
/globalConfig.conf:
--------------------------------------------------------------------------------
1 | {"proxy":{"Host":"127.0.0.1","Port":"1080","Socks5":true,"Enabled":true}}
--------------------------------------------------------------------------------
/src/favicon2.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/src/favicon2.ico
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/plugins.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vs/AigisPlayer/v15/.suo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/.vs/AigisPlayer/v15/.suo
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/app/fa/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/src/app/fa/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const AppConfig = {
2 | production: false,
3 | environment: 'LOCAL'
4 | };
5 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const AppConfig = {
2 | production: true,
3 | environment: 'PROD'
4 | };
5 |
--------------------------------------------------------------------------------
/src/app/fa/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/src/app/fa/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/src/app/fa/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/src/app/fa/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/src/app/res-manager/al.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class ALService {}
5 |
--------------------------------------------------------------------------------
/src/app/core/languageList.ts:
--------------------------------------------------------------------------------
1 | export const LanguageList = {
2 | cn: '简体中文',
3 | jp: '日本語'
4 | };
5 | export const Langs = ['cn', 'jp'];
6 |
--------------------------------------------------------------------------------
/src/app/fa/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/src/app/fa/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/app/fa/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DogReactor/AigisPlayer/HEAD/src/app/fa/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/src/app/global/config.ts:
--------------------------------------------------------------------------------
1 | export class GlobalConfig {
2 | static Host = 'api.pigtv.moe';
3 | static PluginAssets = 'player.pigtv.moe';
4 | }
5 |
--------------------------------------------------------------------------------
/.vs/VSWorkspaceState.json:
--------------------------------------------------------------------------------
1 | {
2 | "ExpandedNodes": [
3 | ""
4 | ],
5 | "SelectedNode": "\\e2e",
6 | "PreviewInSolutionExplorer": false
7 | }
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | height: 100%;
6 | width: 100%;
7 | z-index: 1000;
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/uiFramework/uiframework.component.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | flex-shrink: 0;
5 | height: 100%;
6 | }
7 |
--------------------------------------------------------------------------------
/assets/js/prompt.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer, remote } = require('electron');
2 | window.prompt = (message, text) => {
3 | return ipcRenderer.sendSync('prompt', message, text);
4 | };
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/plugins.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/webview.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive } from '@angular/core';
2 |
3 | @Directive({
4 | selector: 'webview'
5 | })
6 | export class WebviewDirective {
7 | constructor() {}
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/uiFramework/webview.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive } from '@angular/core';
2 |
3 | @Directive({
4 | selector: 'webview'
5 | })
6 | export class WebviewDirective {
7 | constructor() {}
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/res-manager/res-manager.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
2 |
3 | @NgModule({
4 | providers: []
5 | })
6 | export class GlobalModule {}
7 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | /* tslint:disable */
4 | export class AngularElectronPage {
5 | navigateTo(route: string) {
6 | return browser.get(route);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/game/game.component.scss:
--------------------------------------------------------------------------------
1 | webview {
2 | height: 100%;
3 | width: 100%;
4 | }
5 |
6 | div {
7 | height: 100%;
8 | }
9 |
10 | :host {
11 | height: 100%;
12 | width: 100%;
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/plugin/webview.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive } from '@angular/core';
2 |
3 | @Directive({
4 | selector: 'webview'
5 | })
6 | export class WebviewDirective {
7 | constructor() {}
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/util/util.component.scss:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | float: right;
3 | }
4 |
5 | :host {
6 | display: flex;
7 | height: 100%;
8 | }
9 |
10 | .container {
11 | width: 100%;
12 | }
13 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var nodeModule: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
7 | declare var window: Window;
8 | interface Window {
9 | process: any;
10 | require: any;
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/proxy/proxy.component.scss:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | float: right;
3 | }
4 |
5 | :host {
6 | display: flex;
7 | height: 100%;
8 | }
9 |
10 | .container {
11 | width: 100%;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/game/webview.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, Input } from '@angular/core';
2 |
3 | @Directive({
4 | selector: 'webview'
5 | })
6 | export class WebviewDirective {
7 | @Input() preload: string;
8 | constructor() { }
9 | }
10 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es2015",
7 | "types":[
8 | "jasmine",
9 | "node"
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 120,
4 | "tabWidth": 2,
5 | "useTabs": false,
6 | "semi": true,
7 | "overrides": [
8 | {
9 | "files": ".prettierrc",
10 | "options": {
11 | "parser": "json"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | platform:
2 | - x64
3 |
4 | cache:
5 | - node_modules
6 | - '%APPDATA%\npm-cache'
7 | - '%USERPROFILE%\.electron'
8 |
9 | install:
10 | - ps: Install-Product node 10 x64
11 | - npm install
12 |
13 | build_script:
14 | - npm run electron:publish
15 |
16 | test: off
--------------------------------------------------------------------------------
/assets/js/pluginWindowPreload.js:
--------------------------------------------------------------------------------
1 | require('./pluginHelper');
2 | global.ipcrenderer = require('electron').ipcRenderer;
3 |
4 | ipcrenderer.on('plugin-info', (sender, message) => {
5 | const pluginHelper = new PluginHelper();
6 | pluginHelper.plugin = message;
7 | run(pluginHelper);
8 | })
--------------------------------------------------------------------------------
/src/app/uiFramework/components/navbar/navbar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
AigisPlayer {{ appVersion }}
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/components/gameselect/gameselect.component.html:
--------------------------------------------------------------------------------
1 |
7 | {{ game.Name }}
8 |
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | const routes: Routes = [];
5 |
6 | @NgModule({
7 | imports: [RouterModule.forRoot(routes, { useHash: true })],
8 | exports: [RouterModule]
9 | })
10 | export class AppRoutingModule {}
11 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "es2015",
6 | "baseUrl": "",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts",
12 | "dist",
13 | "app-builds",
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/assets/js/inject.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer } = require('electron');
2 |
3 | require('./pluginHelper');
4 | require('./fixPage');
5 | require('./autoLogin');
6 | // require('./loadModule');
7 | require('./prompt');
8 |
9 | // window.require = undefined;
10 | window.exports = undefined;
11 | window.module = undefined;
12 |
13 | console.log('preload script injected');
--------------------------------------------------------------------------------
/src/app/global/global.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
2 | import { GlobalSettingService } from './globalSetting.service';
3 | import { GlobalStatusService } from './globalStatus.service';
4 |
5 | @NgModule({
6 | providers: [GlobalSettingService, GlobalStatusService]
7 | })
8 | export class GlobalModule {}
9 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AigisPlayer
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app/core/game.model.ts:
--------------------------------------------------------------------------------
1 | import { Size } from './util';
2 |
3 | export class GameModel {
4 | public Name: string;
5 | public Size: Size;
6 | public URL: string;
7 | public Spec = '';
8 |
9 | constructor(name: string, size: Size, url: string, spec?: string) {
10 | this.Name = name;
11 | this.Size = size;
12 | this.URL = url;
13 | this.Spec = spec ? spec : '';
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { ElModule } from 'element-angular';
2 | import { NgModule } from '@angular/core';
3 | import { TranslateModule } from '@ngx-translate/core';
4 | import { CommonModule } from '@angular/common';
5 | import { FormsModule } from '@angular/forms';
6 |
7 | @NgModule({
8 | exports: [ElModule, CommonModule, FormsModule, TranslateModule]
9 | })
10 | export class SharedModule {}
11 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/dialog/dialog.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ cancelText }}
5 | {{ okText }}
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/map/map.component.scss:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | float: right;
3 | }
4 |
5 | :host {
6 | display: flex;
7 | }
8 |
9 | hr {
10 | height: 1px;
11 | border: none;
12 | border-top: 1px dashed #d3dce6;
13 | }
14 |
15 | .item {
16 | display: flex;
17 | justify-content: space-between;
18 | }
19 | .container {
20 | width: 100%;
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/game/game.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { GameComponent } from './game.component';
3 | import { WebviewDirective } from './webview.directive';
4 | import { SharedModule } from '../../../shared.module';
5 |
6 | @NgModule({
7 | declarations: [GameComponent, WebviewDirective],
8 | imports: [SharedModule],
9 | exports: [GameComponent]
10 | })
11 | export class GameModule {}
12 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AngularElectronPage } from './app.po';
2 | import { browser, element, by } from 'protractor';
3 |
4 | describe('angular-electron App', () => {
5 | let page: AngularElectronPage;
6 |
7 | beforeEach(() => {
8 | page = new AngularElectronPage();
9 | });
10 |
11 | it('should display message saying App works !', () => {
12 | expect(element(by.css('app-home h1')).getText()).toMatch('App works !');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/environments/environment.dev.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `index.ts`, but if you do
3 | // `ng build --env=prod` then `index.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const AppConfig = {
7 | production: false,
8 | environment: 'DEV'
9 | };
10 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { AppConfig } from './environments/environment';
6 |
7 | if (AppConfig.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule, {
13 | preserveWhitespaces: false
14 | })
15 | .catch(err => console.error(err));
16 |
--------------------------------------------------------------------------------
/src/app/core/keyMapper.ts:
--------------------------------------------------------------------------------
1 | export class KeyMapper {
2 | X: number;
3 | Y: number;
4 | Width: number;
5 | Height: number;
6 | Name: string;
7 | constructor(Name, X, Y, Width, Height) {
8 | this.Name = Name;
9 | this.X = X;
10 | this.Y = Y;
11 | this.Width = Width;
12 | this.Height = Height;
13 | }
14 | }
15 | export const KeyMapperList = [
16 | new KeyMapper('SpeedUpKey', 876, 39, 50, 20),
17 | new KeyMapper('UseSkillKey', 874, 589, 20, 10)
18 | ];
19 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/plugin/plugin.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ item.pluginName }}
4 | {{ item.version }}
5 |
6 |
7 | {{ item.description }}
8 |
9 |
10 | {{ item.author }}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/assets/js/loadModule.js:
--------------------------------------------------------------------------------
1 | ipcrender.on('load-module', (event, plugin) => {
2 | pluginHelper.plugin = plugin;
3 | require(plugin.inject);
4 | });
5 |
6 | const { game, pluginList } = ipcrender.sendSync('require-plugins');
7 |
8 | pluginList.forEach((plugin) => {
9 | if (plugin.inject && plugin.game.indexOf(game.Spec) >= 0) {
10 | const pluginHelper = new PluginHelper();
11 | pluginHelper.plugin = plugin;
12 | require(plugin.inject).run(pluginHelper);
13 | }
14 | })
--------------------------------------------------------------------------------
/src/app/core/hotkey.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { GameService } from './game.service';
3 | import { GlobalSettingService } from '../global/globalSetting.service';
4 | import { ElectronService } from './electron.service';
5 |
6 | @Injectable()
7 | export class HotkeyService {
8 | constructor(
9 | private gameService: GameService,
10 | private globalSettingService: GlobalSettingService,
11 | private electronService: ElectronService
12 | ) { }
13 | }
14 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "target": "es2015",
7 | "baseUrl": "",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ],
20 | "exclude": [
21 | "dist",
22 | "app-builds",
23 | "node_modules"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/dialog/dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-uiframe-dialog',
5 | templateUrl: './dialog.component.html',
6 | styleUrls: ['./dialog.component.scss']
7 | })
8 | export class DialogComponent {
9 | @Input() visible: boolean;
10 | @Input() title: string;
11 | @Input() okText: string;
12 | @Input() cancelText: string;
13 | @Input() onOk: Function;
14 | @Input() onCancel: Function;
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/statusbar.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../../../shared.module';
3 | import { UIFrameStatusBarComponent } from './statusbar.component';
4 | import { UIFrameStatusBarToolBarModule } from './toolbar/toolbar.module';
5 |
6 | @NgModule({
7 | declarations: [UIFrameStatusBarComponent],
8 | imports: [SharedModule, UIFrameStatusBarToolBarModule],
9 | exports: [UIFrameStatusBarComponent]
10 | })
11 | export class UIFrameStatusBarModule {}
12 |
--------------------------------------------------------------------------------
/src/app/uiFramework/uiframework.component.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import "~element-angular/theme/index.css";
3 | @import "./app/fa/css/font-awesome.min.css";
4 | html, body {
5 | margin: 0;
6 | padding: 0;
7 | overflow:hidden;
8 | height: 100%;
9 | }
10 | ::-webkit-scrollbar{
11 | width:0px;
12 | }
13 | *{
14 | outline:none;
15 | user-select:none;
16 | cursor: default;
17 | font-family:'Helvetica Neue',Helvetica,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',SimSun,sans-serif;
18 | }
--------------------------------------------------------------------------------
/src/app/gameData/gamedata.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { DebuggerService } from './debugger.service';
4 | import { AigisGameDataService } from './aigis/aigis.service';
5 | import { AigisStatisticsService } from './aigis/statistics.service';
6 |
7 | @NgModule({
8 | imports: [CommonModule],
9 | providers: [DebuggerService, AigisGameDataService, AigisStatisticsService]
10 | })
11 | export class GameDataModule {}
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | osx_image: xcode8.3
2 |
3 | dist: trusty
4 | sudo: false
5 |
6 | language: node_js
7 | node_js: "10"
8 |
9 | env:
10 | global:
11 | - ELECTRON_CACHE=$HOME/.cache/electron
12 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
13 |
14 | os:
15 | - linux
16 | - osx
17 |
18 | cache:
19 | directories:
20 | - node_modules
21 | - $HOME/.cache/electron
22 | - $HOME/.cache/electron-builder
23 | - $HOME/.npm/_prebuilds
24 |
25 | install:
26 | - npm install
27 | script:
28 | - npm run electron:publish
29 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/game/game.component.html:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/plugins.modules.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../../../../../../../../shared.module';
3 | import { SettingPluginsComponent } from './plugins.component';
4 | import { PluginsPluginComponent } from './components/plugin.component';
5 |
6 | @NgModule({
7 | declarations: [SettingPluginsComponent, PluginsPluginComponent],
8 | imports: [SharedModule],
9 | exports: [SettingPluginsComponent]
10 | })
11 | export class SettingPluginsModule {}
12 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/plugin/plugin.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { UIFrameStatusBarToolBarPluginComponent } from './plugin.component';
3 | import { WebviewDirective } from './webview.directive';
4 | import { SharedModule } from '../../../../../../shared.module';
5 |
6 | @NgModule({
7 | declarations: [UIFrameStatusBarToolBarPluginComponent, WebviewDirective],
8 | imports: [SharedModule],
9 | exports: [UIFrameStatusBarToolBarPluginComponent]
10 | })
11 | export class UIFrameStatusBarToolBarPluginModule {}
12 |
--------------------------------------------------------------------------------
/tsconfig-serve.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "target": "es5",
9 | "types": [
10 | "node"
11 | ],
12 | "lib": [
13 | "es2017",
14 | "es2016",
15 | "es2015",
16 | "dom"
17 | ]
18 | },
19 | "include": [
20 | "main.ts"
21 | ],
22 | "exclude": [
23 | "node_modules",
24 | "**/*.spec.ts"
25 | ]
26 | }
--------------------------------------------------------------------------------
/src/app/uiFramework/components/main/main.component.scss:
--------------------------------------------------------------------------------
1 | .box {
2 | display: flex;
3 | flex-direction: row;
4 | height: 100%;
5 | }
6 |
7 | .game-main {
8 | display: block;
9 | flex: 1;
10 | }
11 |
12 | .top-box {
13 | display: flex;
14 | width: 100%;
15 | }
16 |
17 | .bottom-plugin {
18 | background: grey;
19 | flex: 1;
20 | }
21 |
22 | :host {
23 | flex: 1 1 auto;
24 | flex-wrap: nowrap;
25 | display: flex;
26 | background: white;
27 | }
28 |
29 | .obs {
30 | background: black;
31 | }
32 |
33 | webview {
34 | height: 100%;
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/components/gameselect/gameselect.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 300px;
3 | display: flex;
4 | height: 400px;
5 | flex-direction: column;
6 | color: black;
7 | background: #FFFFFF;
8 | overflow: scroll;
9 | }
10 |
11 | .selection {
12 | padding: 10px 10px 10px 20px;
13 | height: 30px;
14 | line-height: 30px;
15 | transition: all 0.5s;
16 | }
17 |
18 | .selection:hover {
19 | background: #58B7FF;
20 | color: white;
21 | }
22 |
23 | .selected {
24 | background: #20A0FF;
25 | color: white;
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/gameData/aigis/util.ts:
--------------------------------------------------------------------------------
1 | export class Base64 {
2 | static Encode = bytes => {
3 | const binary = [];
4 | const len = bytes.byteLength;
5 | for (let i = 0; i < len; i++) {
6 | binary.push(String.fromCharCode(bytes[i]));
7 | }
8 | return window.btoa(binary.join(''));
9 | };
10 |
11 | static Decode = str => {
12 | const binary_str = window.atob(str);
13 | const len = binary_str.length;
14 | const bytes = new Buffer(len);
15 | for (let i = 0; i < len; i++) {
16 | bytes[i] = binary_str.charCodeAt(i);
17 | }
18 | return bytes;
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { ElectronService } from './electron.service';
5 | import { GameService } from './game.service';
6 | import { PluginService } from './plugin.service';
7 | import { HotkeyService } from './hotkey.service';
8 | import { LogService } from './log.service';
9 |
10 | @NgModule({
11 | imports: [CommonModule],
12 | providers: [ElectronService, GameService, PluginService, HotkeyService, LogService]
13 | })
14 | export class CoreModule {}
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "es2016",
17 | "es2015",
18 | "dom"
19 | ]
20 | },
21 | "include": [
22 | "main.ts",
23 | "src/**/*"
24 | ],
25 | "exclude": [
26 | "node_modules"
27 | ]
28 | }
--------------------------------------------------------------------------------
/tsconfig-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "outDir": "./dist",
6 | "moduleResolution": "node",
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "target": "es5",
10 | "types": [
11 | "node"
12 | ],
13 | "lib": [
14 | "es2017",
15 | "es2016",
16 | "es2015",
17 | "dom"
18 | ]
19 | },
20 | "include": [
21 | "main.ts"
22 | ],
23 | "exclude": [
24 | "node_modules",
25 | "**/*.spec.ts"
26 | ]
27 | }
--------------------------------------------------------------------------------
/postinstall.js:
--------------------------------------------------------------------------------
1 | // Allow angular using electron module (native node modules)
2 | const fs = require('fs');
3 | const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
4 |
5 | fs.readFile(f_angular, 'utf8', function (err, data) {
6 | if (err) {
7 | return console.log(err);
8 | }
9 | var result = data.replace(/target: "electron-renderer",/g, '');
10 | var result = result.replace(/target: "web",/g, '');
11 | var result = result.replace(/return \{/g, 'return {target: "electron-renderer",');
12 |
13 | fs.writeFile(f_angular, result, 'utf8', function (err) {
14 | if (err) return console.log(err);
15 | });
16 | });
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/setting.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 800px;
3 | max-width: 100%;
4 | display: flex;
5 | height: 500px;
6 | flex-direction: column;
7 | color: black;
8 | background: #F9FAFC;
9 | }
10 |
11 | @media screen and (max-width: 500px) {
12 | :host {
13 | width: 400px;
14 | }
15 | }
16 |
17 | .container {
18 | height: 100%;
19 | }
20 |
21 | .content {
22 | height: 100%;
23 | background: #fff;
24 | overflow-x: hidden;
25 | }
26 |
27 | .menu {
28 | overflow-x: hidden;
29 | overflow-y: overlay;
30 | height: 100%;
31 | }
32 |
33 | .content-item {
34 | opacity: 100;
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/plugin/plugin.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 300px;
3 | max-height: 500px;
4 | background: white;
5 | overflow: scroll;
6 | }
7 |
8 | .container {
9 | padding: 10px 10px 10px 20px;
10 | transition: all 0.5s;
11 | }
12 |
13 | .container:hover {
14 | background: #58B7FF;
15 | color: white;
16 | }
17 |
18 | .title {
19 | font-size: 17px;
20 | font-weight: bold;
21 | }
22 |
23 | .version {
24 | font-size: 10px;
25 | margin-left: 10px;
26 | color: #8492A6;
27 | }
28 |
29 | .description {
30 | font-size: 15px;
31 | }
32 |
33 | .author {
34 | font-size: 11px;
35 | font-weight: bold;
36 | color: #8492A6;
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/core/log.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ipcRenderer } from 'electron';
3 | @Injectable()
4 | export class LogService {
5 | private url = '';
6 | RequestCount = 0;
7 | ResponseCount = 0;
8 | ErrorCount = 0;
9 | constructor() {
10 | ipcRenderer.on('request-incoming', () => {
11 | this.RequestCount++;
12 | });
13 | ipcRenderer.on('response-incoming', () => {
14 | this.ResponseCount++;
15 | });
16 | ipcRenderer.on('error-incoming', (_, req, err) => {
17 | this.ErrorCount++;
18 | });
19 | }
20 | get Url() {
21 | return this.url;
22 | }
23 | set Url(url: string) {
24 | this.url = url;
25 | // this.RequestCount = 0;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/plugins.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { Plugin, PluginService } from '../../../../../../../../core/plugin.service';
4 |
5 | @Component({
6 | selector: 'app-setting-plugins',
7 | templateUrl: './plugins.component.html',
8 | styleUrls: ['./plugins.component.scss']
9 | })
10 | export class SettingPluginsComponent {
11 | plugins: Plugin[];
12 | constructor(private pluginService: PluginService) {
13 | // http
14 | this.getPluginList();
15 | }
16 | async getPluginList() {
17 | this.plugins = await this.pluginService.getPluginListFromRemote();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/dialog/dialog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { DialogComponent } from './dialog.component';
4 |
5 | describe('DialogComponent', () => {
6 | let component: DialogComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [DialogComponent]
12 | }).compileComponents();
13 | }));
14 |
15 | beforeEach(() => {
16 | fixture = TestBed.createComponent(DialogComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | });
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/navbar/navbar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { ElectronService } from '../../../core/electron.service';
3 |
4 | @Component({
5 | selector: 'app-uiframe-navbar',
6 | templateUrl: './navbar.component.html',
7 | styleUrls: ['./navbar.component.scss']
8 | })
9 | export class UIFrameNavbarComponent {
10 | @Input() dialogToggle: Function;
11 | appVersion: string;
12 | constructor(private electronService: ElectronService) {
13 | this.appVersion = this.electronService.APP.getVersion();
14 | }
15 |
16 | minimize() {
17 | this.electronService.currentWindow.minimize();
18 | }
19 |
20 | toggle() {
21 | if (this.dialogToggle) {
22 | this.dialogToggle('navbar');
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/components/accountselect/accountselect.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 200px;
3 | display: flex;
4 | height: 200px;
5 | flex-direction: column;
6 | color: black;
7 | background: #FFFFFF;
8 | overflow: scroll;
9 | }
10 |
11 | .selection {
12 | padding: 10px 10px 10px 20px;
13 | height: 30px;
14 | line-height: 30px;
15 | transition: all 0.5s;
16 | }
17 |
18 | .selection:hover {
19 | background: #58B7FF;
20 | color: white;
21 | }
22 |
23 | .selected {
24 | background: #20A0FF;
25 | color: white;
26 | }
27 |
28 | .password-container {
29 | text-align: center;
30 | }
31 |
32 | .password-error {
33 | color: #FF4949
34 | }
35 |
36 | .password-clear {
37 | margin: 10px;
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/gameData/debugger.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { WebContents, webContents } from 'electron';
3 | import { ElectronService } from '../core/electron.service';
4 | import { RequestHandler } from '../../../requestHandler';
5 |
6 | @Injectable()
7 | export class DebuggerService {
8 | requestHandler: typeof RequestHandler;
9 | constructor(private electronService: ElectronService) {
10 | this.requestHandler = this.electronService.APP['RequestHandler'] as typeof RequestHandler;
11 | this.requestHandler.Clear();
12 | }
13 | Subscribe(
14 | options: { url: Array; method?: string; request?: boolean },
15 | callback: (url: string, response, request?: any) => void
16 | ) {
17 | this.requestHandler.Subscribe(options, callback);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/toolbar.component.scss:
--------------------------------------------------------------------------------
1 | .tbtn {
2 | font-size: 16px;
3 | height: 30px;
4 | width: 40px;
5 | line-height: 30px;
6 | text-align: center;
7 | color: white;
8 | transition: all 0.5s;
9 | }
10 |
11 | .tbtn:hover {
12 | color: black;
13 | background: #D3DCE6;
14 | }
15 |
16 | :host {
17 | display: flex;
18 | flex-direction: column-reverse;
19 | padding: 0 0px 0 9px;
20 | }
21 |
22 | .toolbar {
23 | display: flex;
24 | background: #1F2D3D;
25 | }
26 |
27 | .menu-container {
28 | display: flex;
29 | flex-direction: column;
30 | align-items: flex-end;
31 | }
32 |
33 | .selected {
34 | background: #FFF;
35 | color: black;
36 | }
37 |
38 | .menu {
39 | position: fixed;
40 | bottom: 30px;
41 | }
42 |
--------------------------------------------------------------------------------
/angular.webpack.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Custom angular webpack configuration
3 | */
4 |
5 | module.exports = (config, options) => {
6 | config.target = 'electron-renderer';
7 | // config.externals = {'aigis-fuel':'window.require("aigis-fuel")'}
8 | if (options.customWebpackConfig.target) {
9 | config.target = options.customWebpackConfig.target;
10 | } else if (options.fileReplacements) {
11 | for(let fileReplacement of options.fileReplacements) {
12 | if (fileReplacement.replace !== 'src/environments/environment.ts') {
13 | continue;
14 | }
15 |
16 | let fileReplacementParts = fileReplacement['with'].split('.');
17 | if (['dev', 'prod', 'test', 'electron-renderer'].indexOf(fileReplacementParts[1]) < 0) {
18 | config.target = fileReplacementParts[1];
19 | }
20 | break;
21 | }
22 | }
23 |
24 | return config;
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/statusbar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{
6 | gameService.slowTick ? '0.25×' : '1×'
7 | }}
8 |
9 |
10 |
{{ logService.Url }}
11 |
12 | (
13 | {{ logService.ResponseCount }}/ {{ logService.ErrorCount }} )
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/navbar/navbar.component.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 25px;
3 | background: white;
4 | -webkit-app-region: drag;
5 | }
6 |
7 | .title {
8 | margin: 0;
9 | font-size: 12px;
10 | line-height: 24px;
11 | padding-left: 7px; // font-family: Verdana, Geneva, Tahoma, sans-serif;
12 | float: left;
13 | }
14 |
15 | .btn {
16 | padding-left: 7px;
17 | padding-right: 7px;
18 | padding-top: 3px;
19 | padding-bottom: 3px;
20 | -webkit-app-region: no-drag;
21 | transition: all 0.2s;
22 | float: right;
23 | }
24 |
25 | .btn:hover {
26 | background: #D3DCE6;
27 | color: white;
28 | }
29 |
30 | .close:hover {
31 | background: #FF4949;
32 | color: white;
33 | }
34 |
35 | .btn-container {
36 | display: flex;
37 | justify-content: space-around;
38 | }
39 |
--------------------------------------------------------------------------------
/assets/js/autoLogin.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer, remote } = require('electron');
2 |
3 | ipcRenderer.on('login', (event, message) => {
4 | var login_id = document.getElementById('login_id');
5 | var password = document.getElementById('password');
6 | if (login_id != undefined && password != undefined) {
7 | var form = login_id.parentNode.parentNode.parentNode;
8 | const event = new Event('input', { bubbles: true });
9 | login_id.value = message.username;
10 | password.value = message.password;
11 | login_id.dispatchEvent(event);
12 | password.dispatchEvent(event);
13 | var btn = form[5];
14 | if (btn.type !== 'submit') {
15 | btn = form[5];
16 | }
17 | setTimeout(function () {
18 | btn.click();
19 | }, 1000);
20 | } else {
21 | ipcRenderer.sendToHost('loginerror');
22 | }
23 | //document.getElementById
24 | })
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /app-builds
8 | /release
9 | main.js
10 | requestHandler.js
11 | src/**/*.js
12 | !src/karma.conf.js
13 | *.js.map
14 | /plugins
15 | filelist.json
16 | updateInfo.json
17 |
18 | # dependencies
19 | /node_modules
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | .vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
36 |
37 | # misc
38 | /.sass-cache
39 | /connect.lock
40 | /coverage
41 | /libpeerconnection.log
42 | npm-debug.log
43 | testem.log
44 | /typings
45 |
46 | # e2e
47 | /e2e/*.js
48 | !/e2e/protractor.conf.js
49 | /e2e/*.map
50 |
51 | # System Files
52 | .DS_Store
53 | Thumbs.db
54 |
--------------------------------------------------------------------------------
/src/app/uiFramework/uiframework.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../shared.module';
3 |
4 | import { GameModule } from './modules/game/game.module';
5 | import { UIFrameStatusBarModule } from './modules/statusbar/statusbar.module';
6 |
7 | import { UIFrameComponent } from './uiframework.component';
8 | import { UIFrameMainComponent } from './components/main/main.component';
9 | import { UIFrameNavbarComponent } from './components/navbar/navbar.component';
10 | import { WebviewDirective } from './webview.directive';
11 | import { DialogComponent } from './components/dialog/dialog.component';
12 |
13 | @NgModule({
14 | declarations: [UIFrameComponent, UIFrameMainComponent, UIFrameNavbarComponent, WebviewDirective, DialogComponent],
15 | imports: [GameModule, SharedModule, UIFrameStatusBarModule],
16 | exports: [UIFrameComponent]
17 | })
18 | export class UiFrameModule {}
19 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/account/account.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | height: 100%;
4 | }
5 |
6 | .add {
7 | width: 100%;
8 | padding: 20px 0 0 0;
9 | text-align: center;
10 | }
11 |
12 | .checkbox {
13 | float: right;
14 | }
15 |
16 | .btn {
17 | text-align: center;
18 | }
19 |
20 | .selectmenu {
21 | display: flex;
22 | justify-content: space-between;
23 | }
24 |
25 | .editor {
26 | margin-top: 20px;
27 | }
28 |
29 | .password-container {
30 | text-align: center;
31 | display: flex;
32 | height: 100%;
33 | justify-content: center;
34 | flex-direction: column;
35 | width: 100%;
36 | }
37 |
38 | .password-error {
39 | color: #ff4949;
40 | }
41 |
42 | .password-clear {
43 | margin: 10px;
44 | }
45 |
46 | hr {
47 | height: 1px;
48 | border: none;
49 | border-top: 1px dashed #d3dce6;
50 | }
51 |
52 | .container {
53 | width: 100%;
54 | }
55 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/setting.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../../../../core/game.service';
4 | import { GlobalSettingService } from '../../../../../../global/globalSetting.service';
5 | import { trigger, state, style, animate, transition } from '@angular/animations';
6 |
7 | @Component({
8 | selector: 'app-uiframe-statusbar-toolbar-setting',
9 | templateUrl: './setting.component.html',
10 | styleUrls: ['./setting.component.scss'],
11 | animations: [
12 | trigger('fadeInOut', [
13 | transition(':enter', [style({ opacity: 0 }), animate(0)]),
14 | transition(':leave', [animate(0, style({ opacity: 0 }))])
15 | ])
16 | ]
17 | })
18 | export class UIFrameStatusBarToolBarSettingComponent {
19 | index: String = 'util';
20 | constructor(private gameService: GameService, private globalSettingService: GlobalSettingService) {}
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/dialog/dialog.component.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 24px;
3 | background: white;
4 | display: flex;
5 | flex-flow: row;
6 | justify-content: space-between;
7 | align-content: center;
8 | -webkit-app-region: drag;
9 | }
10 |
11 | .title {
12 | margin: 0;
13 | font-size: 12px;
14 | line-height: 24px;
15 | padding-left: 7px; // font-family: Verdana, Geneva, Tahoma, sans-serif;
16 | }
17 |
18 | .controller {
19 | display: flex;
20 | flex-flow: row;
21 | align-items: center;
22 | }
23 |
24 | .btn {
25 | padding-left: 7px;
26 | padding-right: 7px;
27 | padding-top: 3px;
28 | padding-bottom: 3px;
29 | -webkit-app-region: no-drag;
30 | transition: all 0.2s;
31 | }
32 |
33 | .btn:hover {
34 | background: #D3DCE6;
35 | color: white;
36 | }
37 |
38 | .close:hover {
39 | background: #FF4949;
40 | color: white;
41 | }
42 |
43 | .btn-container {
44 | display: flex;
45 | justify-content: space-around;
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/toolbar.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../../../../shared.module';
3 | import { UIFrameStatusBarToolBarComponent } from './toolbar.component';
4 | import { UIFrameStatusBarToolBarGameSelectComponent } from './components/gameselect/gameselect.component';
5 | import { UIFrameStatusBarToolBarSettingModule } from './modules/setting/setting.module';
6 | import { UIFrameStatusBarToolBarAccountSelectComponent } from './components/accountselect/accountselect.component';
7 | import { UIFrameStatusBarToolBarPluginModule } from './modules/plugin/plugin.module';
8 |
9 | @NgModule({
10 | declarations: [
11 | UIFrameStatusBarToolBarComponent,
12 | UIFrameStatusBarToolBarGameSelectComponent,
13 | UIFrameStatusBarToolBarAccountSelectComponent
14 | ],
15 | imports: [SharedModule, UIFrameStatusBarToolBarSettingModule, UIFrameStatusBarToolBarPluginModule],
16 | exports: [UIFrameStatusBarToolBarComponent]
17 | })
18 | export class UIFrameStatusBarToolBarModule {}
19 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/map/map.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy } from '@angular/core';
2 | import { GameService } from '../../../../../../../../core/game.service';
3 | import { GlobalSettingService } from '../../../../../../../../global/globalSetting.service';
4 | import { GlobalStatusService } from '../../../../../../../../global/globalStatus.service';
5 |
6 | @Component({
7 | selector: 'app-setting-map',
8 | templateUrl: './map.component.html',
9 | styleUrls: ['./map.component.scss']
10 | })
11 | export class SettingMapComponent {
12 | constructor(
13 | private gameService: GameService,
14 | public globalSettingService: GlobalSettingService,
15 | private globalStatusService: GlobalStatusService
16 | ) {}
17 | keyup(target, event: KeyboardEvent) {
18 | event.preventDefault();
19 | event.stopPropagation();
20 | this.globalSettingService.GlobalSetting[target] = event.code;
21 | }
22 | clear(target) {
23 | this.globalSettingService.GlobalSetting[target] = '';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 25000,
8 | getPageTimeout: 15000,
9 | delayBrowserTimeInSeconds: 0,
10 | specs: [
11 | './e2e/**/*.e2e-spec.ts'
12 | ],
13 | capabilities: {
14 | 'browserName': 'chrome',
15 | chromeOptions: {
16 | binary: './node_modules/electron/dist/electron.exe',
17 | args: ['--test-type=webdriver', 'app=dist/main.js']
18 | }
19 | },
20 | directConnect: true,
21 | baseUrl: 'http://localhost:4200/',
22 | framework: 'jasmine2',
23 | jasmineNodeOpts: {
24 | showColors: true,
25 | defaultTimeoutInterval: 30000,
26 | print: function () { }
27 | },
28 | beforeLaunch: function () {
29 | require('ts-node').register({
30 | project: 'e2e/tsconfig.e2e.json'
31 | });
32 | },
33 | onPrepare() {
34 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "com.dogreactor.aigisplayer",
3 | "productName": "AigisPlayer",
4 | "directories": {
5 | "app": "dist",
6 | "output": "app-builds",
7 | "buildResources": "build"
8 | },
9 | "publish": [
10 | {
11 | "provider": "generic",
12 | "url": "http://player.pigtv.moe/assets/aigisplayer"
13 | },
14 | {
15 | "provider": "github"
16 | },
17 | {
18 | "provider": "s3",
19 | "bucket": "player.pigtv.moe",
20 | "path": "assets/aigisplayer",
21 | "publishAutoUpdate": true,
22 | "channel": "latest"
23 | }
24 | ],
25 | "nsis": {
26 | "artifactName": "${productName}-Setup-${version}.${ext}"
27 | },
28 | "win": {
29 | "icon": "./build/icon-256.png",
30 | "target": [
31 | {
32 | "target": "nsis",
33 | "arch": ["x64"]
34 | },
35 | "zip"
36 | ]
37 | },
38 | "mac": {
39 | "icon": "./build/icons",
40 | "target": ["dmg"]
41 | },
42 | "linux": {
43 | "category": "Game",
44 | "icon": "./build/icons",
45 | "target": ["deb", "AppImage"]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/setting.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { SharedModule } from '../../../../../../shared.module';
4 | import { UIFrameStatusBarToolBarSettingComponent } from './setting.component';
5 | import { SettingProxyComponent } from './components/proxy/proxy.component';
6 | import { SettingUtilComponent } from './components/util/util.component';
7 | import { SettingAccountComponent } from './components/account/account.component';
8 | import { SettingMapComponent } from './components/map/map.component';
9 | import { SettingPluginsModule } from './modules/plugins/plugins.modules';
10 |
11 | @NgModule({
12 | declarations: [
13 | UIFrameStatusBarToolBarSettingComponent,
14 | SettingProxyComponent,
15 | SettingUtilComponent,
16 | SettingAccountComponent,
17 | SettingMapComponent,
18 | ],
19 | imports: [SharedModule, ReactiveFormsModule, SettingPluginsModule],
20 | exports: [UIFrameStatusBarToolBarSettingComponent]
21 | })
22 | export class UIFrameStatusBarToolBarSettingModule {}
23 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/components/gameselect/gameselect.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Output, EventEmitter } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../../../../core/game.service';
4 | import { GameModel } from '../../../../../../core/game.model';
5 | import { GlobalStatusService } from '../../../../../../global/globalStatus.service';
6 |
7 | @Component({
8 | selector: 'app-uiframe-statusbar-toolbar-gameselect',
9 | templateUrl: './gameselect.component.html',
10 | styleUrls: ['./gameselect.component.scss']
11 | })
12 | export class UIFrameStatusBarToolBarGameSelectComponent {
13 | gameList: GameModel[];
14 | @Output() onSelected = new EventEmitter();
15 | constructor(private gameService: GameService, private globalStatusService: GlobalStatusService) {
16 | this.gameList = gameService.GameInfo.filter(g => g.Name !== 'None');
17 | }
18 | selectGame(index) {
19 | this.onSelected.emit();
20 | const game = this.gameList[index];
21 | this.globalStatusService.GlobalStatusStore.Get('CurrentGame').Dispatch(game);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
11 |
12 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
13 | declare const __karma__: any;
14 | declare const require: any;
15 |
16 | // Prevent Karma from running prematurely.
17 | __karma__.loaded = function() {};
18 |
19 | // First, initialize the Angular testing environment.
20 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
21 | // Then we find all the tests.
22 | const context = require.context('./', true, /\.spec\.ts$/);
23 | // And load the modules.
24 | context.keys().map(context);
25 | // Finally, start Karma to run the tests.
26 | __karma__.start();
27 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/statusbar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { GameService } from '../../../core/game.service';
3 | import { LogService } from '../../../core/log.service';
4 |
5 | @Component({
6 | selector: 'app-uiframe-statusbar',
7 | templateUrl: './statusbar.component.html',
8 | styleUrls: ['./statusbar.component.scss']
9 | })
10 | export class UIFrameStatusBarComponent {
11 | @Input() dialogToggle: Function;
12 |
13 | constructor(public gameService: GameService, public logService: LogService) {}
14 |
15 | screenshot(event) {
16 | if (event.button === 0) {
17 | this.gameService.ScreenShot();
18 | } else {
19 | this.gameService.ScreenShot(true);
20 | }
21 | }
22 |
23 | toggle() {
24 | if (this.dialogToggle) {
25 | this.dialogToggle('statusbar');
26 | }
27 | }
28 |
29 | pause() {
30 | this.gameService.emitEvent('aigis-pause');
31 | }
32 | slowTick() {
33 | this.gameService.SlowTick = !this.gameService.SlowTick;
34 | }
35 |
36 | isAigisOrShirore() {
37 | return this.gameService.CurrentGame.Spec === 'aigis' || this.gameService.CurrentGame.Spec === 'oshiro';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/statusbar.component.scss:
--------------------------------------------------------------------------------
1 | .btn {
2 | font-size: 24px;
3 | padding: 3px 10px 3px 10px;
4 | color: white;
5 | }
6 |
7 | .btn:hover {
8 | color: black;
9 | background: #d3dce6;
10 | }
11 | .log {
12 | display: flex;
13 | flex: 1;
14 | justify-content: center;
15 | height: 100%;
16 | align-items: center;
17 | color: white;
18 | max-width: 60%;
19 | font-size: 12px;
20 | .url {
21 | max-width: 100%;
22 | white-space: nowrap;
23 | text-overflow: ellipsis;
24 | overflow: hidden;
25 | }
26 | .count {
27 | margin-left: 10px;
28 | margin-right: 10px;
29 | flex-shrink: 0;
30 | .success {
31 | color: #13ce66;
32 | }
33 | .error {
34 | color: #ff4949;
35 | }
36 | }
37 | }
38 | :host {
39 | height: 30px;
40 | background: #475669;
41 | display: flex;
42 | align-items: flex-end;
43 | justify-content: space-between;
44 | z-index: 100;
45 | }
46 |
47 | .text {
48 | margin: 0;
49 | height: 100%;
50 | font-size: 20px;
51 | padding-left: 5px;
52 | padding-right: 5px;
53 | }
54 |
55 | .btns-container {
56 | display: flex;
57 | justify-content: center;
58 | align-items: center;
59 | }
60 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/toolbar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Renderer2 } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../../core/game.service';
4 | import { GlobalStatusService } from '../../../../global/globalStatus.service';
5 | import { trigger, state, style, animate, transition } from '@angular/animations';
6 |
7 | @Component({
8 | selector: 'app-uiframe-statusbar-toolbar',
9 | templateUrl: './toolbar.component.html',
10 | styleUrls: ['./toolbar.component.scss'],
11 | animations: [
12 | trigger('fadeInOut', [
13 | transition(':enter', [style({ opacity: 0 }), animate(100)]),
14 | transition(':leave', [animate(100, style({ opacity: 0 }))])
15 | ])
16 | ]
17 | })
18 | export class UIFrameStatusBarToolBarComponent {
19 | selectedMenu: String;
20 | constructor(private globalStatusService: GlobalStatusService, private renderer: Renderer2) {}
21 | selectMenu(menu) {
22 | if (menu === this.selectedMenu) {
23 | this.closeMenu();
24 | } else {
25 | this.selectedMenu = menu;
26 | }
27 | }
28 | closeMenu() {
29 | this.selectedMenu = 'none';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/components/plugin.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ plugin.pluginName }}
5 | {{ plugin.version }}
6 |
7 |
8 | {{ plugin.description }}
9 |
10 |
11 | {{ plugin.author }}
12 |
13 |
14 |
15 |
16 |
24 |
25 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/components/accountselect/accountselect.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
{{ 'UTIL.PASSWORDERROR' | translate }}
16 |
17 | {{
18 | 'UTIL.FORCECLEARPASSWORD' | translate
19 | }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 | {{ account.Name }}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/proxy/proxy.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/app/gameData/aigis/EventList.ts:
--------------------------------------------------------------------------------
1 | export const Event = {
2 | // fileList 文件列表
3 | EeLcL7hN: 'quest-success', // statusUpdate
4 | uD69xeaG: 'quest-start', // statusUpdate
5 | shxnpXtj: 'login-status', // statusUpdate
6 | qX5kSDt2: 'login-status2', // statusUpdate
7 | '0kR1cNJJ': 'inin-result', // statusUpdate
8 | udh6JQRa: 'base-gacha-result',
9 | btcntJ9k: 'sp-gacha-result',
10 | yObCmn3i: 'premium1-gacha-result',
11 | plXgfdjN: 'premium2-gacha-result',
12 | Zm4xQHVP: 'fame-gacha-result',
13 | Nc3RTbni: 'white-guarantee-gacha',
14 | bnz8xWXB: 'unit-move',
15 | oS5aZ5ll: 'allunits-info',
16 | pP8JgbjO: 'unit-sell',
17 | igmn1XCf: 'buy-charisma',
18 | QxZpjdfV: 'all-quest-info',
19 | uE23SxBr: 'none',
20 | d4YRCAQa: 'none',
21 | GRs733a4: 'allcards-info', // 全单位信息
22 | foi6moes: 'none',
23 | TPCta1SK: 'time-init',
24 | R5FHPbQb: 'watch',
25 | i4u2L2LJ: 'orb-init',
26 | Y0d4Yhj1: 'none',
27 | E935RTof: 'none',
28 | PeMDvjps: 'test',
29 | jWbtv5NR: 'heart', // 心跳
30 | AekvKZk6: 'none',
31 | zzdfsknw: 'present-info',
32 | eZ5wrQTH: 'crystal-change',
33 | kgiqvp4a: 'crystal-init',
34 | aB2hnQXL: 'new-gacha-result',
35 | rEphfdmU: 'spirit-repo',
36 | c9LvZMPA: 'silver-repo',
37 | XL1FmueE: 'affection-up',
38 | '7jzKZrvU': 'exp-up',
39 | '89b8NiGE': 'cc',
40 | QGbFwDWV: 'aw1',
41 | E5bTYnSK: 'aw2',
42 | G4uBbPjZ: 'disband'
43 | };
44 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/setting.component.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
19 |
20 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | files: [
19 | { pattern: './src/test.ts', watched: false }
20 | ],
21 | preprocessors: {
22 | './src/test.ts': ['@angular/cli']
23 | },
24 | mime: {
25 | 'text/x-typescript': ['ts','tsx']
26 | },
27 | coverageIstanbulReporter: {
28 | reports: [ 'html', 'lcovonly' ],
29 | fixWebpackSourcePaths: true
30 | },
31 | angularCli: {
32 | environment: 'dev'
33 | },
34 | reporters: config.angularCli && config.angularCli.codeCoverage
35 | ? ['progress', 'coverage-istanbul']
36 | : ['progress', 'kjhtml'],
37 | port: 9876,
38 | colors: true,
39 | logLevel: config.LOG_INFO,
40 | autoWatch: true,
41 | browsers: ['Chrome'],
42 | singleRun: false
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/src/app/core/util.ts:
--------------------------------------------------------------------------------
1 | export class Point {
2 | public X: number;
3 | public Y: number;
4 | }
5 |
6 | export class Size {
7 | public Height: number;
8 | public Width: number;
9 |
10 | constructor(height: number, width: number) {
11 | this.Height = height;
12 | this.Width = width;
13 | }
14 | }
15 |
16 | export function AddSetterToObject(object, callback: (v: any) => any) {
17 | for (const key in object) {
18 | if (object.hasOwnProperty(key)) {
19 | let newKey = key;
20 | if (newKey[0].toLowerCase() === newKey[0]) {
21 | continue;
22 | }
23 | newKey = newKey[0].toLowerCase() + newKey.substring(1);
24 | object[newKey] = object[key];
25 | Object.defineProperty(object, key, {
26 | set: function(v) {
27 | this[newKey] = v;
28 | callback(v);
29 | },
30 | get: function() {
31 | return this[newKey];
32 | }
33 | });
34 | }
35 | }
36 | }
37 |
38 | export function getArgs(func) {
39 | // 先用正则匹配,取得符合参数模式的字符串.
40 | // 第一个分组是这个: ([^)]*) 非右括号的任意字符
41 | const args = func.toString().match(/\(([^)]*)\)/)[1];
42 |
43 | // 用逗号来分隔参数(arguments string).
44 | return args
45 | .split(',')
46 | .map(function(arg) {
47 | // 去除注释(inline comments)以及空格
48 | return arg.replace(/\/\*.*\*\//, '').trim();
49 | })
50 | .filter(function(arg) {
51 | // 确保没有 undefined.
52 | return arg;
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/src/app/core/store.ts:
--------------------------------------------------------------------------------
1 | import * as Rx from 'rxjs';
2 | class State {
3 | Key: string;
4 | private value: any;
5 | private subject: Rx.Subject;
6 | private type: string;
7 | constructor(key: string, value: any) {
8 | this.Key = key;
9 | this.value = value;
10 | this.subject = new Rx.Subject();
11 | this.type = typeof value;
12 | }
13 | get Value() {
14 | return this.value;
15 | }
16 | public Dispatch(value) {
17 | if (typeof value !== this.type) {
18 | throw `Type ${typeof value} Does not match ${this.type}`;
19 | } else {
20 | this.value = value;
21 | this.subject.next(value);
22 | }
23 | }
24 | public Subscribe(callback: (v: any) => void) {
25 | callback(this.value);
26 | return this.subject.subscribe(callback);
27 | }
28 | }
29 |
30 | export class Store {
31 | private store: State[] = [];
32 | constructor(states?: {}) {
33 | if (!states) {
34 | return;
35 | }
36 | for (const key in states) {
37 | if (states.hasOwnProperty(key)) {
38 | this.Add(key, states[key]);
39 | }
40 | }
41 | }
42 | public Add(key, value) {
43 | const state = new State(key, value);
44 | this.store.push(state);
45 | }
46 | public Get(key) {
47 | const state = this.store.find(v => {
48 | return v.Key === key;
49 | });
50 | if (!state) {
51 | return null;
52 | } else {
53 | return state;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/assets/i18n/jp.json:
--------------------------------------------------------------------------------
1 | {
2 | "SETTING": {
3 | "PROXY": {
4 | "PROXY": "プロキシ",
5 | "HOST": "ホスト",
6 | "PORT": "ポート",
7 | "SOCKS5": "Socks5プロトコル",
8 | "ENABLE": "有効する"
9 | },
10 | "UTIL": {
11 | "VER": "バージョン",
12 | "UTIL": "一般",
13 | "LOCK": "常に手前に",
14 | "MUTE": "ミュート",
15 | "ZOOM": "サイズ",
16 | "LANGUAGE": "言語/Language",
17 | "DEFAULTGAME": "既定のゲーム",
18 | "DATACOLLECT": "データ収集"
19 | },
20 | "ACCOUNT": {
21 | "ACCOUNT": "アカウント管理",
22 | "NAME": "ニックネーム",
23 | "USERNAME": "ユーザー名",
24 | "PASSWORD": "パスワード",
25 | "DEFAULT": "デフォルト(非暗号化のみ)",
26 | "ACCOUNTLISTPASSWORD": "暗号用パスワード"
27 | },
28 | "MAP": {
29 | "MAP": "ホットキー",
30 | "SPEEDUP": "二倍速",
31 | "USESKILL": "スキル発動",
32 | "SCREENSHOT": "スクリーンショット"
33 | }
34 |
35 | },
36 | "UTIL": {
37 | "ACCEPT": "確認",
38 | "CANCEL": "キャンセル",
39 | "SAVE": "セーブ",
40 | "PASSWORDERROR": "パスワードが間違います",
41 | "INPUTPASSWORD": "パスワードを入力してください",
42 | "FORCECLEARPASSWORD": "パスワードを強制削除",
43 | "PLEASESELECT": "選んでください",
44 | "ADD": "添加",
45 | "DELETE": "削除",
46 | "CLEAR": "クリアする"
47 | },
48 | "MESSAGE": {
49 | "PAGE-DIDNOT-LOAD": "このページにアクセスできません,接続やプロキシを確認してください",
50 | "SCREENSHOT-SUCCESS": "クリップボードに画面をキャプチャできました!CTRL+Vで貼り付けてください",
51 | "SET-SUCCESS": "セット完了",
52 | "SAVE-SUCCESS": "セーブ完了",
53 | "LOAD-FINISH": "",
54 | "DECRYPT-SUCCESS": "復号完了"
55 | }
56 | }
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/proxy/proxy.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 | import { GameService } from '../../../../../../../../core/game.service';
4 | import { GlobalSettingService, ProxyRule } from '../../../../../../../../global/globalSetting.service';
5 | import { Proxy } from '../../../../../../../../global/globalSetting.service';
6 | import { TranslateService } from '@ngx-translate/core';
7 |
8 | @Component({
9 | selector: 'app-setting-proxy',
10 | templateUrl: './proxy.component.html',
11 | styleUrls: ['./proxy.component.scss']
12 | })
13 | export class SettingProxyComponent {
14 | proxyForm: FormGroup;
15 | rules: string[];
16 | translations: {};
17 | constructor(
18 | private gameService: GameService,
19 | private globalSettingService: GlobalSettingService,
20 | private fb: FormBuilder,
21 | private translateService: TranslateService
22 | ) {
23 | this.createForm(globalSettingService.GlobalSetting.Proxy);
24 | this.observeChange();
25 | this.rules = Object.keys(ProxyRule);
26 | translateService.get(this.rules.map(v => 'SETTING.PROXY.TYPES.' + v.toUpperCase())).subscribe(v => {
27 | this.translations = v;
28 | });
29 | }
30 |
31 | createForm(proxy: Proxy) {
32 | this.proxyForm = this.fb.group(proxy);
33 | }
34 |
35 | observeChange() {
36 | this.proxyForm.valueChanges.forEach(value => this.globalSettingService.setProxy(value));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/assets/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "SETTING": {
3 | "PROXY": {
4 | "PROXY": "代理",
5 | "HOST": "主机",
6 | "PORT": "端口",
7 | "SOCKS5": "是否为Socks5协议",
8 | "ENABLE": "启用"
9 | },
10 | "UTIL": {
11 | "VER": "Version",
12 | "UTIL": "Util",
13 | "LOCK": "Lock",
14 | "MUTE": "Mute",
15 | "ZOOM": "Zoom",
16 | "DATACOLLECT":"Data Collection"
17 | },
18 | "ACCOUNT": {
19 | "ACCOUNT": "账户管理",
20 | "NAME": "昵称",
21 | "USERNAME": "用户名",
22 | "PASSWORD": "密码",
23 | "DEFAULT": "是否为默认账户(仅未加密)",
24 | "ACCOUNTLISTPASSWORD": "设置加密密码"
25 | },
26 | "MAP": {
27 | "MAP":"键盘映射/热键",
28 | "SPEEDUP":"二倍速",
29 | "USESKILL":"使用技能",
30 | "SCREENSHOT":"截图"
31 | }
32 |
33 | },
34 | "UTIL": {
35 | "ACCEPT": "确认",
36 | "CANCEL": "取消",
37 | "SAVE": "保存",
38 | "PASSWORDERROR": "密码错误",
39 | "INPUTPASSWORD": "请输入密码",
40 | "FORCECLEARPASSWORD": "强制清除密码",
41 | "PLEASESELECT": "请选择",
42 | "ADD": "添加",
43 | "DELETE": "删除"
44 | },
45 | "MESSAGE":{
46 | "PAGE-DIDNOT-LOAD":"页面加载失败,请确认代理是否正确配置。",
47 | "SCREENSHOT-SUCCESS":"截图到剪贴板成功!请使用CTRL+V粘贴。",
48 | "SET-SUCCESS":"设置成功",
49 | "SAVE-SUCCESS":"保存成功",
50 | "LOADING-FINISH":"程序加载完毕",
51 | "DECRYPT-SUCCESS":"解密成功"
52 | }
53 | }
--------------------------------------------------------------------------------
/src/app/uiFramework/components/main/main.component.html:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
34 |
35 | 为补充游戏攻略、便利玩家游戏,从AigisPlayer
36 | Ver2.1.10起我们增加了数据收集功能。将向AigisPlayer的发布服务器发送玩家在部分游戏活动中产生的匿名、一次性、不可追踪的游玩记录。
37 |
38 |
39 |
40 | AigisPlayer保证采集的数据和分析数据得到的结果是公开、非盈利的,在收集数据的过程中不主动向游戏服务器发送任何非玩家操作引起的请求,收集到的数据也不会发往除AigisPlayer发布服务器以外的地方。如果您不同意AigisPlayer采集数据的行为或内容,可以到设置菜单里关闭后重启程序。
41 |
42 |
43 | 目前我们将收集以下数据
44 |
45 | - 千年战争Aigis:进入任务所获得的单位和物品掉落信息,以及对玩家单位库分析后得到的掉落率修正
46 |
47 | 开启数据收集后可以使用依赖此行为的插件,例如掉落统(jù)计(tòu)。
48 | 感谢您的分享(<ゝω・)~☆
49 |
50 | 知道了
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ElectronService } from './core/electron.service';
3 | import { TranslateService } from '@ngx-translate/core';
4 | import { GlobalStatusService } from './global/globalStatus.service';
5 | import { GlobalSettingService } from './global/globalSetting.service';
6 | import { GameService } from './core/game.service';
7 | import { Langs } from './core/languageList';
8 | import { HotkeyService } from './core/hotkey.service';
9 |
10 | @Component({
11 | selector: 'app-root',
12 | templateUrl: './app.component.html',
13 | styleUrls: ['./app.component.scss']
14 | })
15 | export class AppComponent {
16 | constructor(
17 | public electronService: ElectronService,
18 | private translate: TranslateService,
19 | private globalStatusService: GlobalStatusService,
20 | private globalSettingService: GlobalSettingService,
21 | private gameService: GameService,
22 | private hotkeyService: HotkeyService
23 | ) {
24 | // translation
25 | translate.addLangs(Langs);
26 | translate.setDefaultLang('cn');
27 | translate.use('cn');
28 |
29 | globalSettingService.Init();
30 | if (electronService.isElectron()) {
31 | console.log('Mode electron');
32 | // Check if electron is correctly injected (see externals in webpack.config.js)
33 | console.log('electron is correctly injected?', electronService.ipcRenderer);
34 | // Check if nodeJs childProcess is correctly injected (see externals in webpack.config.js)
35 | console.log('nodeJs childProcess is correctly injected?', electronService.childProcess);
36 | } else {
37 | console.log('Mode web');
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/toolbar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
40 |
--------------------------------------------------------------------------------
/assets/js/gbf/fixSlideBar.js:
--------------------------------------------------------------------------------
1 | let platformList = ['isGree', 'isDMM', 'isYahoo'];
2 |
3 | let html = document.documentElement.outerHTML;
4 | let needAppend = false;
5 | for (let platform of platformList) {
6 | let pattern = new RegExp(`${platform}:\\s*function\\(\\)\\{\\s*return true;\\s*\\}`);
7 | if (pattern.test(html)) {
8 | needAppend = true;
9 | break;
10 | }
11 | }
12 |
13 | window.addEventListener(
14 | 'blur',
15 | function(e) {
16 | e.stopImmediatePropagation();
17 | },
18 | false
19 | );
20 |
21 | if (!needAppend) {
22 | // 把侧边栏干掉
23 | document.body.children[0].removeChild(document.body.children[0].children[0]);
24 | }
25 |
26 | // // 暂时先return掉,等以后支持维拉了再说
27 | // return;
28 |
29 | // let prevNode;
30 | // for (let node of document.head.children) {
31 | // if (node.tagName === 'META' && node.name === 'apple-mobile-web-app-title') {
32 | // prevNode = node;
33 | // } else if (
34 | // node.tagName === 'SCRIPT' &&
35 | // node.src === 'https://cdn-connect.mobage.jp/jssdk/mobage-menubar.2.4.2.min.js'
36 | // ) {
37 | // // mobage version
38 | // return;
39 | // }
40 | // }
41 |
42 | // let script = document.createElement('script');
43 | // script.src = 'https://cdn-connect.mobage.jp/jssdk/mobage-menubar.2.4.2.min.js'; // <- the main difference between mobage and other versions
44 | // prevNode.parentNode.insertBefore(script, prevNode.nextSibling);
45 |
46 | // let createCss = function(css) {
47 | // let style = document.createElement('style');
48 | // style.innerHTML = css;
49 | // return style;
50 | // };
51 |
52 | // let appendCss = function(css) {
53 | // document.head.appendChild(createCss(css));
54 | // };
55 |
56 | // let css = 'div[data-menubar-container=MenuBarContainer] > nav > * { display: none; }';
57 | // appendCss(css);
58 |
--------------------------------------------------------------------------------
/assets/js/gif.js:
--------------------------------------------------------------------------------
1 | let timer = null;
2 | let frames = [];
3 |
4 | ipcrender.on('start-record', (event, message) => {
5 | console.log(window.location.host);
6 | console.log(document.domain);
7 | let canvas = undefined;
8 | try {
9 | canvas = window.frames['game_frame'].document.getElementById('aigis').contentWindow.document.getElementById('canvas');
10 | let ctx = canvas.getContext('2d');
11 | //ipcrender.sendToHost('start-record');
12 | if (timer !== null) clearInterval(timer);
13 | frames = [];
14 | //开始录制
15 | timer = setInterval(() => {
16 | let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
17 | frames.push(imgData);
18 | console.log('添加Frames');
19 | }, 200);
20 | }
21 | catch (e) {
22 | ipcrender.sendToHost('failed-to-start-record');
23 | console.log('失败', e);
24 | }
25 | });
26 |
27 | ipcrender.on('stop-record', (event, message) => {
28 | clearInterval(timer);
29 | let count = 0;
30 | ipcrender.sendToHost('add-new-frame', frames[0]);
31 | //ipcrender.sendToHost('start-add-frames');
32 | /*console.log('start-add-frames');
33 | setTimeout(()=>{
34 | let _timer = setInterval(()=>{
35 | console.log(frames[count]);
36 | ipcrender.sendToHost('add-new-frame',frames[count]);
37 | console.log('add-new-frame');
38 | count++;
39 | if(count === frames.length){
40 | clearInterval(_timer);
41 | setTimeout(()=>{
42 | ipcrender.sendToHost('finish-add-frames');
43 | console.log('finish-add-frames');
44 | },1500);
45 | }
46 | },1500);
47 | },1500);*/
48 |
49 | //ipcrender.sendToHost('add-frames',frames);
50 | });
--------------------------------------------------------------------------------
/src/app/gameData/aigis/decode.ts:
--------------------------------------------------------------------------------
1 | import { Base64 } from './util';
2 |
3 | const decode = (buffer, key) => {
4 | const decoded = new Uint8Array(buffer.byteLength);
5 | for (let i = 0; i < buffer.byteLength; i++) {
6 | decoded[i] = buffer[i] ^ key;
7 | }
8 | return decoded;
9 | };
10 |
11 | export class Decoder {
12 | static DecodeList = (buffer: Buffer) => {
13 | const aigisAssetsBegin = 'https://drc1bk94f7rq8.cloudfront.net/';
14 | const b = [];
15 | const d = decode(buffer, 0xea ^ 0x30);
16 | for (let i = 0; i < d.byteLength; i++) {
17 | b.push(String.fromCharCode(d[i]));
18 | }
19 | const csvData = b.join('');
20 | const csvDatas = csvData.split('\n');
21 | const datas: Map = new Map();
22 | for (let i = 0; i < csvDatas.length; i++) {
23 | const data = csvDatas[i].split(',');
24 | /*let obj = {
25 | path : d[0] + '/' + d[1],
26 | type : d[2],
27 | length : d[3],
28 | fileName : d[4]
29 | }
30 | datas.push(obj);*/
31 | datas.set(data[4], aigisAssetsBegin + data[0] + '/' + data[1]);
32 | }
33 |
34 | return datas;
35 | };
36 |
37 | static DecodeXml = (buffer: Buffer) => {
38 | const head = '';
39 | const startByte = head.charCodeAt(0);
40 | for (let i = 0; i < Math.min(100, buffer.byteLength); i++) {
41 | let test = true;
42 | const _b = buffer[i];
43 | for (let j = 1; j < head.length; j++) {
44 | // aaaaa aaaaa bbbbb bbbbb
45 | const testVal = _b ^ buffer[i + j];
46 | const canon = startByte ^ head.charCodeAt(j);
47 | if (testVal !== canon) {
48 | test = false;
49 | break;
50 | }
51 | }
52 | if (test) {
53 | return decode(buffer, startByte ^ _b);
54 | }
55 | }
56 | return null;
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/src/assets/i18n/cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "SETTING": {
3 | "PROXY": {
4 | "PROXY": "代理",
5 | "HOST": "主机",
6 | "PORT": "端口",
7 | "SOCKS5": "是否为Socks5协议",
8 | "ENABLE": "启用",
9 | "TYPE": "代理规则",
10 | "TYPES": {
11 | "OFF": "关闭",
12 | "SHIMAKAZEGO": "岛风GO",
13 | "ACGP": "ACGPower",
14 | "SHADOWSOCKS": "ShadowSocks(R)",
15 | "CUSTOM": "自定义"
16 | }
17 | },
18 | "UTIL": {
19 | "VER": "版本",
20 | "UTIL": "通用",
21 | "LOCK": "保持在最前端",
22 | "MUTE": "静音",
23 | "ZOOM": "缩放",
24 | "OPACITY": "透明度",
25 | "LANGUAGE": "语言/Language",
26 | "DEFAULTGAME": "默认游戏",
27 | "CACHE": "清理缓存",
28 | "OPEN-SCREENSHOT-DIR": "打开截图文件夹",
29 | "DATACOLLECT": "数据收集",
30 | "ACCELERATION": "关闭硬件加速(需重启)"
31 | },
32 | "ACCOUNT": {
33 | "ACCOUNT": "账户管理",
34 | "NAME": "昵称",
35 | "USERNAME": "用户名",
36 | "PASSWORD": "密码",
37 | "DEFAULT": "是否为默认账户(仅未加密)",
38 | "ACCOUNTLISTPASSWORD": "设置加密密码"
39 | },
40 | "MAP": {
41 | "MAP": "键盘映射/热键",
42 | "SPEEDUP": "二倍速",
43 | "USESKILL": "使用技能",
44 | "SCREENSHOT": "截图",
45 | "RELOAD": "刷新",
46 | "PAUSE": "暂停",
47 | "BULLETTIME": "德云时间"
48 | }
49 | },
50 | "UTIL": {
51 | "ACCEPT": "确认",
52 | "CANCEL": "取消",
53 | "SAVE": "保存",
54 | "OPEN": "打开",
55 | "PASSWORDERROR": "密码错误",
56 | "INPUTPASSWORD": "请输入密码",
57 | "FORCECLEARPASSWORD": "强制清除密码",
58 | "PLEASESELECT": "请选择",
59 | "ADD": "添加",
60 | "DELETE": "删除",
61 | "CLEAR": "清除"
62 | },
63 | "MESSAGE": {
64 | "PAGE-DIDNOT-LOAD": "页面加载失败,请确认代理是否正确配置。",
65 | "SCREENSHOT-SUCCESS": "截图到剪贴板成功!请使用CTRL+V粘贴。",
66 | "SCREENSHOT-SAVE-SUCCESS": "截图保存成功!",
67 | "SET-SUCCESS": "设置成功",
68 | "SAVE-SUCCESS": "保存成功",
69 | "LOAD-FINISH": "程序加载完毕",
70 | "DECRYPT-SUCCESS": "解密成功",
71 | "CLEARCACHE-SUCCESS": "缓存清理成功"
72 | }
73 | }
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js/dist/zone-mix';
2 | import 'reflect-metadata';
3 | import '../polyfills';
4 | import { BrowserModule } from '@angular/platform-browser';
5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
6 | import { Routes, RouterModule } from '@angular/router';
7 | import { NgModule } from '@angular/core';
8 | import { FormsModule } from '@angular/forms';
9 | import { CoreModule } from './core/core.module';
10 | import { UiFrameModule } from './uiFramework/uiframework.module';
11 | import { UIFrameComponent } from './uiFramework/uiframework.component';
12 | import { GlobalModule } from './global/global.module';
13 | import { AppRoutingModule } from './app-routing.module';
14 |
15 | import { HttpClientModule, HttpClient } from '@angular/common/http';
16 |
17 | // NG Translate
18 | import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
19 | import { TranslateHttpLoader } from '@ngx-translate/http-loader';
20 | import { AppComponent } from './app.component';
21 |
22 | import { SharedModule } from './shared.module';
23 | import { ElModule } from 'element-angular';
24 | import { WebviewDirective } from './webview.directive';
25 | import { GameDataModule } from './gameData/gamedata.module';
26 |
27 | // AoT requires an exported function for factories
28 | export function HttpLoaderFactory(http: HttpClient) {
29 | return new TranslateHttpLoader(http, './assets/i18n/', '.json');
30 | }
31 |
32 | @NgModule({
33 | declarations: [AppComponent, WebviewDirective],
34 | imports: [
35 | BrowserModule,
36 | BrowserAnimationsModule,
37 | AppRoutingModule,
38 | ElModule.forRoot(),
39 | FormsModule,
40 | HttpClientModule,
41 | TranslateModule.forRoot({
42 | loader: {
43 | provide: TranslateLoader,
44 | useFactory: HttpLoaderFactory,
45 | deps: [HttpClient]
46 | }
47 | }),
48 | CoreModule,
49 | GlobalModule,
50 | GameDataModule,
51 | UiFrameModule
52 | ],
53 | bootstrap: [AppComponent],
54 | exports: [WebviewDirective]
55 | })
56 | export class AppModule {}
57 |
--------------------------------------------------------------------------------
/src/app/gameData/aigis/statistics.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { AigisGameDataService } from './aigis.service';
3 | import { SpoilsStatistics } from './statistics/spoils.statistics';
4 | import { HttpClient } from '@angular/common/http';
5 | import { GlobalConfig } from '../../global/config';
6 | import { GlobalSettingService } from '../../global/globalSetting.service';
7 |
8 | @Injectable()
9 | export class AigisStatisticsService {
10 | private spoilsReport: SpoilsStatistics;
11 | private subscription: Map void>> = new Map();
12 | public DataCollectPermit = true;
13 | public DataCollectNoted = false;
14 | constructor(
15 | private globalSettingService: GlobalSettingService,
16 | private aigisGameDataService: AigisGameDataService,
17 | private http: HttpClient
18 | ) {
19 | if (window.localStorage.getItem('AnnouncedDataCollected')) {
20 | this.DataCollectNoted = JSON.parse(window.localStorage.getItem('AnnouncedDataCollected'));
21 | }
22 | this.DataCollectPermit = this.globalSettingService.GlobalSetting.DataCollectPermit;
23 | if (this.DataCollectPermit) {
24 | this.spoilsReport = new SpoilsStatistics(this);
25 | this.spoilsReport.subscribData(this.aigisGameDataService);
26 | }
27 | }
28 | async sendRecord(record) {
29 | if (this.subscription.has(record.type)) {
30 | this.subscription.get(record.type).forEach(func => {
31 | func(record);
32 | });
33 | }
34 | const url = `https://${GlobalConfig.Host}/statistics/aigis`;
35 | return this.http.post(url, record).subscribe(response => {
36 | console.log(response, record);
37 | });
38 | }
39 | subscribe(label, callback) {
40 | if (this.subscription.has(label)) {
41 | this.subscription.get(label).push(callback);
42 | } else {
43 | this.subscription.set(label, [callback]);
44 | }
45 | }
46 | confirmDataPermit() {
47 | this.DataCollectNoted = true;
48 | window.localStorage.setItem('AnnouncedDataCollected', JSON.stringify(this.DataCollectNoted));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/plugin/plugin.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, AfterViewInit, OnDestroy, Output, EventEmitter } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../../../../core/game.service';
4 | import { GlobalSettingService, Account } from '../../../../../../global/globalSetting.service';
5 | import { GlobalStatusService } from '../../../../../../global/globalStatus.service';
6 | import { ElMessageService } from 'element-angular';
7 | import * as Rx from 'rxjs';
8 | import { WebviewTag, WebContents } from 'electron';
9 | import { ElectronService } from '../../../../../../core/electron.service';
10 | import { PluginService, Plugin } from '../../../../../../core/plugin.service';
11 |
12 | @Component({
13 | selector: 'app-uiframe-statusbar-toolbar-plugin',
14 | templateUrl: './plugin.component.html',
15 | styleUrls: ['./plugin.component.scss']
16 | })
17 | export class UIFrameStatusBarToolBarPluginComponent implements AfterViewInit, OnDestroy {
18 | private subscriptionList: Rx.Subscription[] = [];
19 | public list: Plugin[] = [];
20 | @Output() onSelected = new EventEmitter(); // closeMenu
21 | constructor(
22 | private gameService: GameService,
23 | private globalSettingService: GlobalSettingService,
24 | private globalStatusService: GlobalStatusService,
25 | private message: ElMessageService,
26 | private translateService: TranslateService,
27 | private electronService: ElectronService,
28 | private pluginService: PluginService
29 | ) {
30 | this.list = pluginService.PluginList;
31 | this.subscriptionList.push(
32 | pluginService.ListUpdate.subscribe(v => {
33 | this.list = pluginService.PluginList;
34 | })
35 | );
36 | }
37 | pluginSelect(index) {
38 | this.pluginService.ActivePlugin(this.list[index]);
39 | this.onSelected.emit();
40 | }
41 | ngAfterViewInit() {}
42 | ngOnDestroy() {
43 | for (let i = 0; i < this.subscriptionList.length; i++) {
44 | this.subscriptionList[i].unsubscribe();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Yukimir
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 | Copyright 2017 - Maxime GRIS
24 |
25 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
26 |
27 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/app/uiFramework/uiframework.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { GlobalStatusService } from '../global/globalStatus.service';
3 | import { ElectronService } from '../core/electron.service';
4 | import { ElMessageService } from 'element-angular';
5 | import { GameService } from '../core/game.service';
6 |
7 | export type ToggleCases = 'statusbar' | 'navbar';
8 |
9 | @Component({
10 | selector: 'app-uiframe',
11 | templateUrl: './uiframework.component.html',
12 | styleUrls: ['./uiframework.component.scss']
13 | })
14 | export class UIFrameComponent implements OnInit {
15 | dialogVisible = false;
16 | title = '';
17 | okText = '';
18 | cancelText = '';
19 | currentStatus: ToggleCases;
20 |
21 | constructor(
22 | private gameService: GameService,
23 | private globalStatusService: GlobalStatusService,
24 | private electronService: ElectronService,
25 | private message: ElMessageService
26 | ) {}
27 |
28 | showDialog = (type: ToggleCases) => {
29 | switch (type) {
30 | // 上方导航栏,控制最小化及关闭
31 | case 'navbar':
32 | this.title = '你确定要退出咩';
33 | this.okText = '退出';
34 | break;
35 | // 下方导航栏,控制刷新及截屏
36 | case 'statusbar':
37 | this.title = '你确定要刷新咩';
38 | this.okText = '刷';
39 | break;
40 |
41 | default:
42 | break;
43 | }
44 | this.cancelText = '手滑';
45 | this.currentStatus = type;
46 | this.dialogVisible = true;
47 | };
48 |
49 | reload() {
50 | this.gameService.Reload();
51 | }
52 |
53 | handleOnOk = () => {
54 | switch (this.currentStatus) {
55 | case 'navbar':
56 | this.electronService.APP.exit(0);
57 | break;
58 |
59 | case 'statusbar':
60 | this.reload();
61 | break;
62 |
63 | default:
64 | break;
65 | }
66 | this.dialogVisible = false;
67 | };
68 |
69 | handleOnCancel = () => {
70 | this.dialogVisible = false;
71 | };
72 |
73 | ngOnInit() {
74 | this.electronService.CheckForUpdate(() => {
75 | this.message['success']('新版本下载成功,请在设置菜单确认安装');
76 | this.globalStatusService.GlobalStatusStore.Get('NewVersionAVB').Dispatch(true);
77 | });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/app/uiFramework/components/main/main.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, AfterViewInit, DoCheck, ChangeDetectorRef } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../core/game.service';
4 | import { GlobalStatusService } from '../../../global/globalStatus.service';
5 | import { PluginService } from '../../../core/plugin.service';
6 | import { AigisStatisticsService } from '../../../gameData/aigis/statistics.service';
7 | import { WebviewTag } from 'electron';
8 |
9 | @Component({
10 | selector: 'app-uiframe-main',
11 | templateUrl: './main.component.html',
12 | styleUrls: ['./main.component.scss']
13 | })
14 | export class UIFrameMainComponent implements AfterViewInit {
15 | private zoom = 100;
16 | private leftWidth = 0;
17 | private rightWidth = 0;
18 | DataCollectNote = false;
19 | constructor(
20 | private gameService: GameService,
21 | private globalStatusService: GlobalStatusService,
22 | private pluginService: PluginService,
23 | private changeDetectorRef: ChangeDetectorRef,
24 | private aigisStatisticsService: AigisStatisticsService
25 | ) {
26 | const state = this.globalStatusService.GlobalStatusStore.Get('Zoom');
27 | this.zoom = state.Value;
28 | state.Subscribe(v => {
29 | this.zoom = v;
30 | });
31 | this.DataCollectNote = !this.aigisStatisticsService.DataCollectNoted;
32 | }
33 | ngAfterViewInit() {
34 | // const left = document.getElementById('leftView');
35 | // const right = document.getElementById('rightView');
36 | this.globalStatusService.GlobalStatusStore.Get('LeftPluginWidth').Subscribe(v => {
37 | this.leftWidth = v | 0;
38 | this.changeDetectorRef.detectChanges();
39 | });
40 | this.globalStatusService.GlobalStatusStore.Get('RightPluginWidth').Subscribe(v => {
41 | this.rightWidth = v | 0;
42 | this.changeDetectorRef.detectChanges();
43 | });
44 | // this.pluginService.regEmbedWebview('left', left);
45 | // this.pluginService.regEmbedWebview('right', right);
46 | }
47 | confirmDataPermit() {
48 | this.DataCollectNote = false;
49 | this.aigisStatisticsService.confirmDataPermit();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/gameData/aigis/statistics/parseQuestName.ts:
--------------------------------------------------------------------------------
1 | export class QuestNameList {
2 | private rawDict: Map; Name: Array }> = new Map();
3 | private id2title: Map = new Map();
4 | private zippedDict: Map = new Map();
5 | loadMissionQuestDict(dict: Array<{ MissionID: number; QuestID: number }>) {
6 | const reduce = {};
7 | dict.forEach(pair => {
8 | if (reduce[pair.MissionID]) {
9 | reduce[pair.MissionID].push(pair.QuestID);
10 | } else {
11 | reduce[pair.MissionID] = [pair.QuestID];
12 | }
13 | });
14 | Object.keys(reduce).forEach(mission => {
15 | if (this.rawDict.get(mission)) {
16 | this.rawDict.get(mission).Quests = this.rawDict.get(mission).Quests.concat(reduce[mission]);
17 | } else {
18 | this.rawDict.set(mission, { Quests: reduce[mission], Name: [] });
19 | }
20 | this.tryZip(mission);
21 | });
22 | }
23 | loadQuestNames(sign: string, dict: Array<{ Message: string }>) {
24 | const match = /QuestNameText(\d+)\.atb/.exec(sign);
25 | if (match) {
26 | const mission = match[1];
27 | if (this.rawDict.get(mission)) {
28 | this.rawDict.get(mission).Name = dict.map(e => e.Message);
29 | } else {
30 | this.rawDict.set(mission, { Quests: [], Name: dict.map(e => e.Message) });
31 | }
32 | this.tryZip(mission);
33 | }
34 | }
35 | loadQuestTitle(QuestsId: Array, QuestsTitle: Array) {
36 | for (let i = 0; i < QuestsId.length; ++i) {
37 | this.id2title.set(QuestsId[i], QuestsTitle[i]);
38 | }
39 | this.rawDict.forEach((v, m) => {
40 | this.tryZip(m.toString());
41 | });
42 | }
43 | tryZip(mission: string) {
44 | if (this.id2title.size > 0) {
45 | const info = this.rawDict.get(mission);
46 | const zlog = [];
47 | if (info.Quests.length > 0 && info.Name.length > 0) {
48 | for (let i = 0; i < info.Quests.length; ++i) {
49 | const id = info.Quests[i];
50 | this.zippedDict.set(id, info.Name[this.id2title.get(id)]);
51 | zlog.push([id, info.Name[this.id2title.get(id)]]);
52 | }
53 | }
54 | }
55 | }
56 | getQuestName(id: number) {
57 | return this.zippedDict.get(id);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/assets/js/pluginHelper.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 | class PluginHelper {
3 | constructor() {
4 | this.ipcRenderer = require('electron').ipcRenderer;
5 | }
6 | onMessage(callback) {
7 | const channel = `${this.plugin.id}`;
8 | this.ipcRenderer.on(channel, (event, msg) => {
9 | console.log('get Message From Guest', msg.type, msg);
10 | const salt = msg.salt;
11 | const message = msg.message;
12 | const sendResponse = (message) => {
13 | if (typeof message === 'object') {
14 | message = JSON.parse(JSON.stringify(message));
15 | }
16 | const channel = `${this.plugin.id}-${salt}`;
17 | event.sender.send(channel, message);
18 | }
19 | callback(message, sendResponse);
20 | });
21 | }
22 | sendMessage(message, callback) {
23 | const sendChannel = `${this.plugin.id}`;
24 | const salt = crypto.createHash("md5").update(Math.random().toString()).digest("hex");
25 | const obj = {
26 | salt: salt,
27 | message: message
28 | }
29 |
30 | const replyChannel = `${this.plugin.id}-${salt}`;
31 | if (callback) {
32 | this.ipcRenderer.once(replyChannel, (event, message) => {
33 | callback(message);
34 | });
35 | }
36 |
37 | this.ipcRenderer.send(sendChannel, obj);
38 | }
39 | sendMessageSync(message) {
40 | const channel = `${this.plugin.id}-sync`
41 | return this.ipcRenderer.sendSync(channel, message);
42 | }
43 | createWindow(file, option) {
44 | const remote = require('@electron/remote');
45 | const BrowserWindow = remote.BrowserWindow;
46 | const currentWindow = require('@electron/remote').getCurrentWindow();
47 | const path = require('path');
48 | const url = require('url').format({
49 | protocol: 'file',
50 | slashes: true,
51 | pathname: file
52 | });
53 | if (!option['webPreferences']) {
54 | option['webPreferences'] = {}
55 | }
56 | option['webPreferences']['preload'] = path.join(__dirname, 'pluginWindowPreload.js');
57 | option.parent = currentWindow;
58 | const win = new BrowserWindow(option);
59 | win.loadURL(url);
60 | win.webContents.on('dom-ready', (event) => {
61 | event.sender.send('plugin-info', this.plugin);
62 | });
63 | return win;
64 | }
65 | }
66 |
67 | global['PluginHelper'] = PluginHelper;
68 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/components/plugin.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import * as compareVersions from 'compare-versions';
4 | import { PluginService, Plugin } from '../../../../../../../../../core/plugin.service';
5 | import { ElectronService } from '../../../../../../../../../core/electron.service';
6 |
7 | @Component({
8 | selector: 'app-plugins-plugin',
9 | templateUrl: './plugin.component.html',
10 | styleUrls: ['./plugin.component.scss']
11 | })
12 | export class PluginsPluginComponent implements OnInit {
13 | @Input() plugin: Plugin = new Plugin();
14 | isInstalled = false;
15 | needUpdate = false;
16 | needRestart = false;
17 | isInstalling = false;
18 | isError = false;
19 | constructor(private pluginService: PluginService, private electronService: ElectronService) {}
20 | ngOnInit() {
21 | if (this.plugin.needRestart) {
22 | this.needRestart = true;
23 | } else {
24 | const pluginInstalled = this.pluginService.PluginList.find(v => {
25 | if (v.path === this.plugin.path) {
26 | return true;
27 | } else {
28 | return false;
29 | }
30 | });
31 | if (pluginInstalled) {
32 | this.isInstalled = true;
33 | this.needUpdate = this.needUpdate =
34 | compareVersions(pluginInstalled.version, this.plugin.version) === -1 ? true : false;
35 | if (this.needUpdate) {
36 | this.isInstalling = true;
37 | this.pluginService.updatePlugin(this.plugin);
38 | }
39 | }
40 | this.isInstalling = this.plugin.installing;
41 | }
42 | }
43 | async remove() {
44 | if (await this.pluginService.removePlugin(this.plugin)) {
45 | this.needRestart = true;
46 | }
47 | }
48 | async install() {
49 | this.isInstalling = true;
50 | if (await this.pluginService.installPluginFromRemote(this.plugin)) {
51 | this.needRestart = true;
52 | } else {
53 | this.isInstalling = false;
54 | }
55 | }
56 | async restart() {
57 | this.electronService.Restart();
58 | }
59 | async update() {
60 | console.log('update');
61 | this.isInstalling = true;
62 | if (await this.pluginService.installPluginFromRemote(this.plugin)) {
63 | this.needRestart = true;
64 | } else {
65 | this.isInstalling = false;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": ["node_modules/codelyzer"],
3 | "rules": {
4 | "callable-types": true,
5 | "class-name": true,
6 | "comment-format": [true, "check-space"],
7 | "curly": true,
8 | "eofline": true,
9 | "forin": true,
10 | "import-spacing": true,
11 | "indent": [true, "spaces"],
12 | "interface-over-type-literal": true,
13 | "label-position": true,
14 | "max-line-length": [true, 140],
15 | "member-access": false,
16 | "member-ordering": [true, "static-before-instance", "variables-before-functions"],
17 | "no-arg": true,
18 | "no-bitwise": false,
19 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
20 | "no-construct": true,
21 | "no-debugger": true,
22 | "no-duplicate-variable": true,
23 | "no-empty": false,
24 | "no-empty-interface": true,
25 | "no-eval": true,
26 | "no-inferrable-types": [true, "ignore-params"],
27 | "no-shadowed-variable": false,
28 | "no-string-literal": false,
29 | "no-string-throw": false,
30 | "no-switch-case-fall-through": true,
31 | "no-trailing-whitespace": true,
32 | "no-unused-expression": false,
33 | "no-var-keyword": true,
34 | "object-literal-sort-keys": false,
35 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"],
36 | "prefer-const": true,
37 | "quotemark": [true, "single"],
38 | "radix": true,
39 | "semicolon": ["always"],
40 | "triple-equals": [true, "allow-null-check"],
41 | "typedef-whitespace": [
42 | true,
43 | {
44 | "call-signature": "nospace",
45 | "index-signature": "nospace",
46 | "parameter": "nospace",
47 | "property-declaration": "nospace",
48 | "variable-declaration": "nospace"
49 | }
50 | ],
51 | "unified-signatures": true,
52 | "variable-name": false,
53 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
54 |
55 | "directive-selector": [false, "attribute", "app", "camelCase"],
56 | "component-selector": [true, "element", "app", "kebab-case"],
57 | "use-input-property-decorator": true,
58 | "use-output-property-decorator": true,
59 | "use-host-property-decorator": true,
60 | "no-input-rename": true,
61 | "no-output-rename": true,
62 | "use-life-cycle-interface": true,
63 | "use-pipe-transform-interface": true,
64 | "component-class-suffix": true,
65 | "directive-class-suffix": true
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/global/globalStatus.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import * as Rx from 'rxjs';
3 | import { ElectronService } from '../core/electron.service';
4 | import { GameService } from '../core/game.service';
5 | import { GameModel } from '../core/game.model';
6 | import { Store } from '../core/store';
7 | import { Size } from '../core/util';
8 |
9 | @Injectable()
10 | export class GlobalStatusService {
11 | public GlobalStatusStore = new Store({
12 | Mute: false,
13 | Lock: false,
14 | SelectedAccount: '',
15 | AccountListPassword: '',
16 | AccountListPasswordError: false,
17 | Zoom: 100,
18 | Opacity: 100,
19 | ExtraHeight: 0,
20 | ExtraWidth: 0,
21 | LeftPluginWidth: 0,
22 | RightPluginWidth: 0,
23 | NewVersionAVB: false,
24 | CurrentGame: new GameModel('None', new Size(640, 960), 'about:blank'),
25 | AccountList: new Array()
26 | });
27 | constructor(private electronService: ElectronService) {
28 | this.GlobalStatusStore.Get('Opacity').Subscribe(v => {
29 | document.body.style.opacity = `${v / 100}`;
30 | });
31 | this.GlobalStatusStore.Get('Lock').Subscribe(v => {
32 | electronService.currentWindow.setAlwaysOnTop(v);
33 | });
34 | this.GlobalStatusStore.Get('Zoom').Subscribe(() => {
35 | this.setZoom();
36 | });
37 | this.GlobalStatusStore.Get('ExtraHeight').Subscribe(() => {
38 | this.setZoom();
39 | });
40 | this.GlobalStatusStore.Get('ExtraWidth').Subscribe(() => {
41 | this.setZoom();
42 | });
43 | this.GlobalStatusStore.Get('LeftPluginWidth').Subscribe(() => {
44 | this.setZoom();
45 | });
46 | this.GlobalStatusStore.Get('RightPluginWidth').Subscribe(() => {
47 | this.setZoom();
48 | });
49 | }
50 | setZoom(v?) {
51 | const currentGame = this.GlobalStatusStore.Get('CurrentGame').Value;
52 | const zoom = this.GlobalStatusStore.Get('Zoom').Value;
53 | const extraHeight = this.GlobalStatusStore.Get('ExtraHeight').Value;
54 | const extraWidth = this.GlobalStatusStore.Get('ExtraWidth').Value;
55 | const leftPlugin = this.GlobalStatusStore.Get('LeftPluginWidth').Value;
56 | const rightPlugin = this.GlobalStatusStore.Get('RightPluginWidth').Value;
57 | this.electronService.ReSize(
58 | new Size(
59 | Math.floor((currentGame.Size.Height + extraHeight) * (zoom / 100)),
60 | Math.floor((currentGame.Size.Width + extraWidth) * (zoom / 100) + leftPlugin + rightPlugin)
61 | )
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/modules/plugins/components/plugin.component.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 10px 20px 10px 20px;
3 | transition: all 0.5s;
4 | display: flex;
5 | justify-content: space-between;
6 | }
7 |
8 | .title {
9 | font-size: 17px;
10 | font-weight: bold;
11 | }
12 |
13 | .version {
14 | font-size: 10px;
15 | margin-left: 10px;
16 | color: #8492A6;
17 | }
18 |
19 | .description {
20 | font-size: 15px;
21 | }
22 |
23 | .author {
24 | font-size: 11px;
25 | font-weight: bold;
26 | color: #8492A6;
27 | }
28 |
29 | .btn-container {
30 | display: flex;
31 | flex-direction: column;
32 | }
33 |
34 | .btn-container button {
35 | margin-bottom: 3px;
36 | }
37 |
38 | .btn-restart {
39 | background-color: #F7BA2A;
40 | border: none;
41 | color: white;
42 | padding: 3px 10px;
43 | text-align: center;
44 | text-decoration: none;
45 | display: inline-block;
46 | }
47 |
48 | .btn-restart:hover {
49 | background-color: rgb(245, 202, 101);
50 | }
51 |
52 | .btn-install {
53 | background-color: #13CE66;
54 | border: none;
55 | color: white;
56 | padding: 3px 10px;
57 | text-align: center;
58 | text-decoration: none;
59 | display: inline-block;
60 | }
61 |
62 | .btn-install:hover {
63 | background-color: rgb(84, 209, 140);
64 | }
65 |
66 | .btn-install:disabled {
67 | background-color: #E5E9F2;
68 | border: none;
69 | color: white;
70 | padding: 3px 10px;
71 | text-align: center;
72 | text-decoration: none;
73 | display: inline-block;
74 | }
75 |
76 | .btn-update {
77 | background-color: #20A0FF;
78 | border: none;
79 | color: white;
80 | padding: 3px 10px;
81 | text-align: center;
82 | text-decoration: none;
83 | display: inline-block;
84 | }
85 |
86 | .btn-update:hover {
87 | background-color: #58B7FF;
88 | }
89 |
90 | .btn-update:disabled {
91 | background-color: #E5E9F2;
92 | border: none;
93 | color: white;
94 | padding: 3px 10px;
95 | text-align: center;
96 | text-decoration: none;
97 | display: inline-block;
98 | }
99 |
100 | .btn-remove {
101 | background-color: #FF4949;
102 | border: none;
103 | color: white;
104 | padding: 3px 10px;
105 | text-align: center;
106 | text-decoration: none;
107 | display: inline-block;
108 | }
109 |
110 | .btn-remove:hover {
111 | background-color: rgb(250, 112, 112);
112 | }
113 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/components/accountselect/accountselect.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Output, EventEmitter, OnDestroy } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../../../../core/game.service';
4 | import { GameModel } from '../../../../../../core/game.model';
5 | import { GlobalStatusService } from '../../../../../../global/globalStatus.service';
6 | import { AccountList, GlobalSettingService } from '../../../../../../global/globalSetting.service';
7 | import * as Rx from 'rxjs';
8 |
9 | @Component({
10 | selector: 'app-uiframe-statusbar-toolbar-accountselect',
11 | templateUrl: './accountselect.component.html',
12 | styleUrls: ['./accountselect.component.scss']
13 | })
14 | export class UIFrameStatusBarToolBarAccountSelectComponent implements OnDestroy {
15 | accountList: AccountList;
16 | selectedAccount: String = '';
17 | subscriptionList: Rx.Subscription[] = [];
18 | accountListPassword = '';
19 | accountListPasswordError = false;
20 | @Output() onSelected = new EventEmitter(); // closeMenu
21 | constructor(private globalStatusService: GlobalStatusService, private globalSettingService: GlobalSettingService) {
22 | this.accountList = this.globalSettingService.AccountList;
23 | this.selectedAccount = globalStatusService.GlobalStatusStore.Get('SelectedAccount').Value;
24 | this.subscriptionList.push(
25 | globalStatusService.GlobalStatusStore.Get('SelectedAccount').Subscribe(v => {
26 | this.selectedAccount = v;
27 | }),
28 | globalStatusService.GlobalStatusStore.Get('AccountListPasswordError').Subscribe(v => {
29 | this.accountListPasswordError = v;
30 | })
31 | );
32 | }
33 | selectAccount(username) {
34 | this.onSelected.emit();
35 | if (this.selectedAccount === username) {
36 | this.globalStatusService.GlobalStatusStore.Get('SelectedAccount').Dispatch('');
37 | } else {
38 | this.globalStatusService.GlobalStatusStore.Get('SelectedAccount').Dispatch(username);
39 | }
40 | }
41 | enterAccountListPassword() {
42 | this.accountListPasswordError = false;
43 | this.globalStatusService.GlobalStatusStore.Get('AccountListPassword').Dispatch(this.accountListPassword);
44 | }
45 | ngOnDestroy() {
46 | for (let i = 0; i < this.subscriptionList.length; i++) {
47 | this.subscriptionList[i].unsubscribe();
48 | }
49 | }
50 | iconClick(e) {
51 | console.log(e);
52 | }
53 | forceClearPassword() {
54 | this.globalSettingService.ForceClearPassword();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/set';
35 |
36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
37 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
38 |
39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
41 |
42 | /** Evergreen browsers require these. **/
43 | import 'core-js/proposals/reflect-metadata';
44 |
45 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
46 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
47 |
48 | /***************************************************************************************************
49 | * Zone JS is required by Angular itself.
50 | */
51 | import 'zone.js/dist/zone-mix'; // Included with Angular CLI.
52 |
53 | /***************************************************************************************************
54 | * APPLICATION IMPORTS
55 | */
56 |
57 | /**
58 | * Date, currency, decimal and percent pipes.
59 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
60 | */
61 | // import 'intl'; // Run `npm install --save intl`.
62 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build and Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*' # 匹配所有以v开头的tag,比如 v1.0.0
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | matrix:
12 | include:
13 | - os: windows-latest
14 | node-version: 16.x
15 | architecture: x64
16 | platform: windows
17 | - os: macos-latest
18 | node-version: 16.x
19 | architecture: x64
20 | platform: mac
21 | - os: ubuntu-latest
22 | node-version: 16.x
23 | architecture: x64
24 | platform: linux
25 |
26 | runs-on: ${{ matrix.os }}
27 |
28 | steps:
29 | - uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0
32 |
33 | - name: Use Node.js ${{ matrix.node-version }}
34 | uses: actions/setup-node@v3
35 | with:
36 | node-version: ${{ matrix.node-version }}
37 | architecture: ${{ matrix.architecture }}
38 |
39 | - name: Install system dependencies (Linux)
40 | if: runner.os == 'Linux'
41 | run: |
42 | sudo apt-get update
43 | sudo apt-get install -y libarchive-tools rpm
44 |
45 | - name: Cache Node.js modules (Windows)
46 | if: runner.os == 'Windows'
47 | uses: actions/cache@v4
48 | with:
49 | path: |
50 | node_modules
51 | ~\AppData\Roaming\npm-cache
52 | ~\AppData\Local\electron
53 | ~\AppData\Local\electron-builder
54 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
55 | restore-keys: |
56 | ${{ runner.OS }}-node-
57 |
58 | - name: Cache Node.js modules (macOS)
59 | if: runner.os == 'macOS'
60 | uses: actions/cache@v4
61 | with:
62 | path: |
63 | node_modules
64 | ~/Library/Caches/electron
65 | ~/Library/Caches/electron-builder
66 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
67 | restore-keys: |
68 | ${{ runner.OS }}-node-
69 |
70 | - name: Cache Node.js modules (Linux)
71 | if: runner.os == 'Linux'
72 | uses: actions/cache@v4
73 | with:
74 | path: |
75 | node_modules
76 | ~/.cache/electron
77 | ~/.cache/electron-builder
78 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
79 | restore-keys: |
80 | ${{ runner.OS }}-node-
81 |
82 | - name: Install dependencies
83 | run: npm install
84 |
85 | - name: Build and publish
86 | env:
87 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
89 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
90 | AWS_DEFAULT_REGION: ap-northeast-3
91 | run: npm run electron:${{ matrix.platform }} -- --publish always
92 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/map/map.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | {{ 'UTIL.CLEAR' | translate }}
11 |
12 |
13 |
14 |
15 |
19 | {{ 'UTIL.CLEAR' | translate }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 | {{ 'UTIL.CLEAR' | translate }}
32 |
33 |
34 |
35 |
36 |
37 |
41 | {{ 'UTIL.CLEAR' | translate }}
42 |
43 |
44 |
45 |
46 |
47 |
51 | {{ 'UTIL.CLEAR' | translate }}
52 |
53 |
54 |
55 |
56 |
57 |
61 | {{ 'UTIL.CLEAR' | translate }}
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/account/account.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 | import { GameService } from '../../../../../../../../core/game.service';
4 | import { GlobalSettingService, AccountList, Account } from '../../../../../../../../global/globalSetting.service';
5 | import { GlobalStatusService } from '../../../../../../../../global/globalStatus.service';
6 | import { TranslateService } from '@ngx-translate/core';
7 | import { ElMessageService } from 'element-angular';
8 | import * as Rx from 'rxjs';
9 |
10 | @Component({
11 | selector: 'app-setting-account',
12 | templateUrl: './account.component.html',
13 | styleUrls: ['./account.component.scss']
14 | })
15 | export class SettingAccountComponent implements OnDestroy {
16 | public accountList: AccountList;
17 | private selectedAccount: Account = null;
18 | private accountListPassword = '';
19 | private accountListPasswordError = false;
20 | private subscriptionList: Rx.Subscription[] = [];
21 | constructor(
22 | private gameService: GameService,
23 | private globalSettingService: GlobalSettingService,
24 | private globalStatusService: GlobalStatusService,
25 | private translateService: TranslateService,
26 | private message: ElMessageService
27 | ) {
28 | this.accountList = globalSettingService.AccountList;
29 | this.accountListPassword = this.globalStatusService.GlobalStatusStore.Get('AccountListPassword').Value;
30 | this.subscriptionList.push(
31 | globalStatusService.GlobalStatusStore.Get('AccountListPassword').Subscribe(v => (this.accountListPassword = v)),
32 | globalStatusService.GlobalStatusStore.Get('AccountListPasswordError').Subscribe(v => {
33 | this.accountListPasswordError = v;
34 | })
35 | );
36 | }
37 | newAccount() {
38 | const max = this.accountList.List.push(new Account());
39 | this.selectedAccount = this.accountList.List[max - 1];
40 | }
41 | deleteAccount() {
42 | const i = this.accountList.List.indexOf(this.selectedAccount);
43 | this.accountList.List.splice(i, 1);
44 | this.selectedAccount = null;
45 | this.globalSettingService.SaveAccountList();
46 | }
47 | saveAccount() {
48 | this.globalSettingService.SaveAccountList();
49 | this.translateService.get('MESSAGE.SAVE-SUCCESS').subscribe(res => {
50 | this.message['success'](res);
51 | });
52 | }
53 | setDefault() {
54 | for (let i = 0; i < this.accountList.List.length; i++) {
55 | if (this.accountList.List[i] === this.selectedAccount) {
56 | continue;
57 | }
58 | this.accountList.List[i].IsDefault = false;
59 | }
60 | }
61 | setPassword() {
62 | this.globalStatusService.GlobalStatusStore.Get('AccountListPassword').Dispatch(this.accountListPassword);
63 | }
64 | forceClearPassword() {
65 | this.globalSettingService.ForceClearPassword();
66 | }
67 | ngOnDestroy() {
68 | for (let i = 0; i < this.subscriptionList.length; i++) {
69 | this.subscriptionList[i].unsubscribe();
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/account/account.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
{{ 'UTIL.PASSWORDERROR' | translate }}
14 |
15 | {{
16 | 'UTIL.FORCECLEARPASSWORD' | translate
17 | }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ 'UTIL.ACCEPT' | translate }}
28 |
29 |
30 |
31 |
32 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {{ 'UTIL.SAVE' | translate }}
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/assets/js/aigisAnimate.js:
--------------------------------------------------------------------------------
1 | var targetCanvas = document.createElement('canvas');
2 | targetCanvas.isCustomedCanvas = true;
3 | // ↓临时测试
4 | targetCanvas.width = 1024;
5 | targetCanvas.height = 1024;
6 | var ctx = targetCanvas.getContext('2d');
7 | ctx.fillStyle = 'red';
8 | var width = 150;
9 | var direction = 1;
10 | var id = -1;
11 | // call FileList
12 | var fileList = {};
13 |
14 | var electron = require('electron');
15 | var urlLib = require('url');
16 | var pathLib = require('path');
17 | var fs = require('fs');
18 | require('./gifuct-js');
19 | var basePath = pathLib.join(electron.remote.app.getPath('userData'), 'mods');
20 | electron.remote.ipcMain.on('fileList', (_, arg) => {
21 | fileList = arg;
22 | });
23 |
24 | var frameCount = 0;
25 | var frameList = [];
26 | var meta;
27 | var jump = false;
28 | draw = () => {
29 | if (!jump) {
30 | jump = true;
31 | } else {
32 | jump = false;
33 | id = requestAnimationFrame(draw);
34 | return;
35 | }
36 | ctx.clearRect(0, 0, meta.height, meta.width);
37 |
38 | frameCount = frameCount % frameList.length;
39 | var frame = frameList[frameCount];
40 | if (meta.type === 'gif') {
41 | var frameImageData = ctx.createImageData(frame.dims.width, frame.dims.height);
42 | frameImageData.data.set(frame.patch);
43 | ctx.putImageData(frameImageData, meta.x, meta.y);
44 | } else {
45 | var img = new Image();
46 | img.src = frame;
47 | ctx.drawImage(img, meta.x, meta.y);
48 | }
49 |
50 | frameCount++;
51 | id = requestAnimationFrame(draw);
52 | };
53 |
54 | loadAnimate = url => {
55 | var urlObj = urlLib.parse(url);
56 | var path = urlObj.path;
57 | var fileName = fileList[path];
58 | if (!fileName) {
59 | return null;
60 | }
61 | // 根据文件名寻找文件
62 | var fileNameWithoutExt = fileName.split('.')[0];
63 | var filePath = pathLib.join(basePath, fileNameWithoutExt);
64 | var metaPath = pathLib.join(filePath, 'meta.json');
65 | var framesPath = pathLib.join(filePath, 'frames');
66 | if (!fs.existsSync(filePath) && !fs.existsSync(metaPath) && !fs.existsSync(framesPath)) {
67 | console.log('no such files', fileName);
68 | return null;
69 | }
70 | try {
71 | var metaJSON = fs.readFileSync(metaPath, { encoding: 'utf8' });
72 | meta = JSON.parse(metaJSON);
73 | if (meta.type === 'gif') {
74 | framesPath = pathLib.join(filePath, 'ani.gif');
75 | var gifBuffer = fs.readFileSync(framesPath);
76 | var gif = new GIF(gifBuffer);
77 | frameList = gif.decompressFrames(true);
78 | } else {
79 | frameList = fs.readdirSync(framesPath).map(v => {
80 | var framePath = pathLib.join(framesPath, v);
81 | var frameBase64 = fs.readFileSync(framePath).toString('base64');
82 | var frameURI = 'data:image/png;base64,' + frameBase64;
83 | return frameURI;
84 | });
85 | }
86 | // 停止没有停止的动画
87 | if (id != -1) {
88 | cancelAnimationFrame(id);
89 | id = -1;
90 | }
91 | // 准备就绪,开始渲染
92 | console.log('start');
93 | frameCount = 0;
94 | targetCanvas.height = meta.height;
95 | targetCanvas.width = meta.width;
96 | id = requestAnimationFrame(draw);
97 | return targetCanvas;
98 | } catch {
99 | return null;
100 | }
101 | };
102 |
103 | stopAnimate = () => {
104 | console.log('stopeed');
105 | cancelAnimationFrame(id);
106 | frameList = [];
107 | id = -1;
108 | };
109 |
--------------------------------------------------------------------------------
/src/app/gameData/aigis/xml2json.ts:
--------------------------------------------------------------------------------
1 | class Node {
2 | public name: string;
3 | public args: { [key: string]: string } = {};
4 | public value: string;
5 | public type: "object" | "array";
6 | public obj: { [key: string]: any } | Array = {};
7 | public hasChild = false;
8 | public constructor(tagname?: string) {
9 | if (tagname) {
10 | const rawArgs = tagname.split(' ');
11 | this.name = rawArgs[0];
12 | // 处理标签中的参数
13 | if (rawArgs.length > 1) {
14 | for (let i = 1; i < rawArgs.length; i++) {
15 | const arg = rawArgs[i];
16 | const sa = arg.split('=');
17 | this.args[sa[0]] = sa.length > 1 ? sa[1].replace(/"/g, '') : '';
18 | }
19 | }
20 | if (this.args.T) {
21 | this.obj = [];
22 | }
23 | }
24 | }
25 | public addToContent(node: Node) {
26 | if (this.obj instanceof Array) {
27 | this.obj.push(node.toJSObj());
28 | } else {
29 | this.obj[node.name] = node.toJSObj();
30 | }
31 | this.hasChild = true;
32 | }
33 | public toJSObj() {
34 | if (!this.hasChild) { return this.value; }
35 | // 如果只有一个值,直接返回
36 | if (this.obj instanceof Array && this.obj.length === 1) { return this.obj[0]; }
37 | // 处理Key-Value和ID-STATUS的情况
38 | else {
39 | if (this.obj["KEY"] && this.obj["VALUE"] && this.obj["KEY"] instanceof Array && this.obj["VALUE"] instanceof Array && this.obj["KEY"].length === this.obj["VALUE"].length) {
40 | const newObj: { [key: string]: any } = {};
41 | this.obj["KEY"].forEach((key: string, index: number) => {
42 | const value = this.obj["VALUE"][index];
43 | newObj[key] = value;
44 | });
45 | return newObj;
46 | }
47 | if (this.obj["ID"] && this.obj["STATUS"] && this.obj["ID"] instanceof Array && this.obj["STATUS"] instanceof Array && this.obj["ID"].length === this.obj["STATUS"].length) {
48 | return this.obj["STATUS"];
49 | }
50 | return this.obj;
51 | }
52 | }
53 | }
54 |
55 | class XmlReader {
56 | public data: string;
57 | public position: number = 0;
58 | constructor(data: string) {
59 | this.data = data;
60 | }
61 | getTag(): [string, string] {
62 | // 说明是标签
63 | const tagstart = this.data.indexOf('<', this.position);
64 | const tagend = this.data.indexOf('>', this.position);
65 | if (tagend === -1) {
66 | return [null, null];
67 | }
68 | const tagName = this.data.slice(tagstart + 1, tagend);
69 | const tagContent = tagstart !== this.position ? this.data.slice(this.position, tagstart) : null;
70 | this.position = tagend + 1;
71 | return [tagName, tagContent];
72 | }
73 | }
74 |
75 | export function Xml2json(data: string) {
76 | const root = new Node();
77 | const nodeStack: Node[] = [];
78 | let currentNode: Node = root;
79 | const reader = new XmlReader(data);
80 | while (true) {
81 | let [tagname, value] = reader.getTag();
82 | if (tagname === null) break;
83 | if (tagname.startsWith("?xml")) continue;
84 | if (!tagname.includes("/")) {
85 | // 这是一个起始标签
86 | const node = new Node(tagname);
87 | nodeStack.push(currentNode);
88 | currentNode = node;
89 | } else {
90 | // 这是一个结束标签
91 | currentNode.value = value;
92 | const parentNode = nodeStack.pop();
93 | parentNode.addToContent(currentNode);
94 | currentNode = parentNode;
95 | }
96 | }
97 | return root.obj;
98 | }
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/util/util.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ appVersion }}
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ 'UTIL.CLEAR' | translate }}
31 |
32 |
33 |
34 |
35 | {{ 'UTIL.OPEN' | translate }}
36 |
37 |
38 |
39 |
40 |
45 |
50 |
51 |
52 |
53 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/app/core/electron.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Size } from './util';
3 | import { ElMessageService } from 'element-angular';
4 | import { TranslateService } from '@ngx-translate/core';
5 | import * as remote from '@electron/remote';
6 |
7 | // If you import a module but never use any of the imported values other than as TypeScript types,
8 | // the resulting javascript file will look as if you never imported the module at all.
9 | import * as fs from 'fs';
10 | import { ipcRenderer, BrowserWindow, app, session, Clipboard, clipboard, Tray } from 'electron';
11 | import * as Electron from 'electron';
12 | import * as childProcess from 'child_process';
13 | import { BrowserWindowConstructorOptions } from 'electron/renderer';
14 |
15 | @Injectable()
16 | export class ElectronService {
17 | ipcRenderer: typeof ipcRenderer;
18 | childProcess: typeof childProcess;
19 | currentWindow: BrowserWindow;
20 | APP: typeof app;
21 | Session: typeof session;
22 | fs: typeof fs;
23 | serve: boolean;
24 | electron: typeof Electron;
25 | clipboard: Clipboard;
26 | Tray: typeof Tray;
27 | ipcMain: typeof Electron.ipcMain;
28 | require: typeof remote.require;
29 | remote: typeof remote;
30 | constructor(private message: ElMessageService, private translateService: TranslateService) {
31 | // Conditional imports
32 | if (this.isElectron()) {
33 | this.electron = window.require('electron');
34 | this.require = require('@electron/remote').require;
35 | this.ipcRenderer = this.electron.ipcRenderer;
36 | this.childProcess = window.require('child_process');
37 | this.currentWindow = require('@electron/remote').getCurrentWindow();
38 | this.APP = require('@electron/remote').app;
39 | this.Session = require('@electron/remote').require('electron').session;
40 | this.fs = window.require('fs');
41 | this.serve = require('@electron/remote').process.argv.slice(1).some(val => val === '--serve');
42 | this.clipboard = require('@electron/remote').clipboard;
43 | this.Tray = require('@electron/remote').Tray;
44 | this.ipcMain = require('@electron/remote').ipcMain;
45 | global['currentWindow'] = this.currentWindow;
46 | this.ipcRenderer.send('Hello', 'Hello');
47 | this.remote = require('@electron/remote');
48 | }
49 | }
50 |
51 | isElectron = () => {
52 | return window && window.process && window.process.type;
53 | };
54 |
55 | ReSize = (size: Size) => {
56 | // What the fuck;
57 | this.currentWindow.resizable = true;
58 | this.currentWindow.setSize(size.Width, size.Height + 54, true);
59 | this.currentWindow.resizable = false;
60 | };
61 |
62 | FlashFrame() {
63 | if (!this.currentWindow.isFocused()) {
64 | this.currentWindow.once('focus', () => {
65 | this.currentWindow.flashFrame(false);
66 | });
67 | this.currentWindow.flashFrame(true);
68 | }
69 | }
70 |
71 | async ClearCache() {
72 | await this.Session.fromPartition('persist:request').clearCache();
73 | await this.Session.fromPartition('persist:game').clearCache();
74 | this.translateService.get('MESSAGE.CLEARCACHE-SUCCESS').subscribe(res => {
75 | this.message['success'](res);
76 | });
77 | }
78 | CreateBrowserWindow = (url, option: BrowserWindowConstructorOptions) => {
79 | const win = new remote.BrowserWindow(option);
80 | win.loadURL(url);
81 | return win;
82 | };
83 | Restart() {
84 | this.APP.relaunch();
85 | this.APP.exit(0);
86 | }
87 | CheckForUpdate(callback) {
88 | ipcRenderer.on('update-message', (sender, text, obj) => {
89 | switch (text) {
90 | case 'UPDATE.CHECK':
91 | this.message['warning']('检查新版本中');
92 | break;
93 | case 'UPDATE.AVB':
94 | this.message['warning']('检查到新版本,下载中...');
95 | break;
96 | case 'UPDATE.NOTAVB':
97 | this.message['success']('现在是最新版本');
98 | break;
99 | case 'UPDATE.ERROR':
100 | this.message['error']('检查更新时发生错误');
101 | break;
102 | case 'UPDATE.PROGRESS':
103 | break;
104 | case 'UPDATE.DOWNLOADED':
105 | callback(true);
106 | break;
107 | }
108 | });
109 | ipcRenderer.send('checkForUpdates');
110 | }
111 | UpdateNow() {
112 | ipcRenderer.send('updateNow');
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/statusbar/toolbar/modules/setting/components/util/util.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 | import { GameService, gameInfo } from '../../../../../../../../core/game.service';
4 | import { GlobalSettingService } from '../../../../../../../../global/globalSetting.service';
5 | import { GlobalStatusService } from '../../../../../../../../global/globalStatus.service';
6 | import { Subscription } from 'rxjs';
7 | import { TranslateService } from '@ngx-translate/core';
8 | import { LanguageList } from '../../../../../../../../core/languageList';
9 | import { ElectronService } from '../../../../../../../../core/electron.service';
10 | import { GameModel } from '../../../../../../../../core/game.model';
11 | import { Size } from '../../../../../../../../core/util';
12 | import { shell } from 'electron';
13 | import * as path from 'path';
14 | import { Subject } from 'rxjs';
15 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
16 | @Component({
17 | selector: 'app-setting-util',
18 | templateUrl: './util.component.html',
19 | styleUrls: ['./util.component.scss']
20 | })
21 | export class SettingUtilComponent implements OnDestroy {
22 | utilForm: FormGroup;
23 | private subscriptions: Subscription[] = [];
24 | private languageList = LanguageList;
25 | disableHardwareAcceleration = false;
26 | DataCollectPermit = true;
27 | DefaultGame: GameModel;
28 | updateReady = false;
29 | appVersion: string;
30 | Mute: boolean;
31 | Lock: Boolean;
32 | Zoom: Number;
33 | Opacity: Number;
34 | zoom$ = new Subject();
35 | constructor(
36 | public gameService: GameService,
37 | private globalSettingService: GlobalSettingService,
38 | private fb: FormBuilder,
39 | private globalStatusService: GlobalStatusService,
40 | public translateService: TranslateService,
41 | private electronService: ElectronService
42 | ) {
43 | // 在这里注册切换按钮
44 | this.regProp('Mute');
45 | this.regProp('Lock');
46 | this.regProp('Zoom');
47 | this.regProp('Opacity');
48 | this.DataCollectPermit = this.globalSettingService.GlobalSetting.DataCollectPermit;
49 | this.disableHardwareAcceleration = this.globalSettingService.GlobalSetting.DisableHardwareAcceleration;
50 | this.DefaultGame = this.globalSettingService.GlobalSetting.DefaultGame;
51 | this.appVersion = this.electronService.APP.getVersion();
52 | this.globalStatusService.GlobalStatusStore.Get('NewVersionAVB').Subscribe(v => {
53 | this.updateReady = v;
54 | });
55 | // 注册zoom的防抖
56 | this.zoom$
57 | .pipe(
58 | debounceTime(400),
59 | distinctUntilChanged()
60 | )
61 | .subscribe(v => {
62 | const state = this.globalStatusService.GlobalStatusStore.Get('Zoom');
63 | state.Dispatch(v);
64 | });
65 | }
66 | switchDisableHardwareAcceleration(value) {
67 | this.globalSettingService.revertDisableHardwareAcceleration();
68 | }
69 | changeLanguage(value) {
70 | this.globalSettingService.GlobalSetting.Language = value;
71 | this.translateService.use(value);
72 | }
73 | changeDefaultGame(value) {
74 | this.globalSettingService.GlobalSetting.DefaultGame = value;
75 | }
76 | switchPermit(value) {
77 | this.globalSettingService.GlobalSetting.DataCollectPermit = value;
78 | }
79 | selectSwitch(key) {
80 | const state = this.globalStatusService.GlobalStatusStore.Get(key);
81 | state.Dispatch(!this[key]);
82 | }
83 | changeSlider(key, event) {
84 | const state = this.globalStatusService.GlobalStatusStore.Get(key);
85 | state.Dispatch(event);
86 | }
87 | ngOnDestroy() {
88 | for (let i = 0; i < this.subscriptions.length; i++) {
89 | this.subscriptions[i].unsubscribe();
90 | }
91 | }
92 | clearCache() {
93 | this.electronService.ClearCache();
94 | }
95 | regProp(key, alias?) {
96 | const state = this.globalStatusService.GlobalStatusStore.Get(key);
97 | let k = key;
98 | if (alias) {
99 | k = alias;
100 | }
101 | this[k] = state.Value;
102 | this.subscriptions.push(state.Subscribe(v => (this[k] = v)));
103 | }
104 | openScreenShotDir() {
105 | shell.openPath(path.join(this.electronService.APP.getPath('userData'), 'screenshots'));
106 | }
107 | updateAP() {
108 | this.electronService.UpdateNow();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/proxyServer.ts:
--------------------------------------------------------------------------------
1 | import * as http from 'http';
2 | import * as request from 'request';
3 | import * as express from 'express';
4 | import * as Agent from 'socks5-http-client/lib/Agent';
5 | import * as fs from 'fs';
6 | import { parseAL, AL } from 'aigis-fuel';
7 | import * as path from 'path';
8 | import * as log from 'electron-log';
9 |
10 | log.transports.file.level = 'info';
11 | let mainFontPath = '';
12 |
13 | function parse(buffer) {
14 | const result = parseAL(buffer);
15 | return result;
16 | }
17 |
18 | export class ProxyServer {
19 | FileList = {};
20 | ProxyHost = '127.0.0.1';
21 | ProxyPort = 1080;
22 | ProxyEnable = false;
23 | ProxyIsSocks5 = false;
24 | Port = 0;
25 | createServer(userDataPath: string) {
26 | const app = express();
27 | app.use(function(req, res, next) {
28 | res.setHeader('Access-Control-Allow-Origin', '*');
29 | res.setHeader('Cache-Control', 'immutable');
30 | next();
31 | });
32 | app.use((req, res) => {
33 | const headers = req.headers;
34 | headers.host = 'assets.millennium-war.net';
35 | // 设置代理
36 | const options: any = {
37 | url: 'http://assets.millennium-war.net' + req.path,
38 | headers: headers,
39 | encoding: null,
40 | };
41 | if (this.ProxyEnable === true && this.ProxyIsSocks5 === true) {
42 | options.agentClass = Agent;
43 | options.agentOptions = {
44 | socksHost: this.ProxyHost,
45 | socksPort: this.ProxyPort,
46 | };
47 | }
48 | if (this.ProxyEnable === true && this.ProxyIsSocks5 === false) {
49 | options.proxy = `http://${this.ProxyHost}:${this.ProxyPort}`;
50 | }
51 | let requestFileName = this.FileList[req.path];
52 | if (req.path.indexOf(mainFontPath) !== -1) {
53 | requestFileName = 'MainFont.aft';
54 | }
55 | let modifyFileName = '';
56 | if (requestFileName) {
57 | switch (path.extname(requestFileName)) {
58 | case '.atb':
59 | modifyFileName = requestFileName.replace('.atb', '.txt');
60 | break;
61 | case '.aar':
62 | modifyFileName = requestFileName.replace('.aar', '');
63 | break;
64 | default:
65 | modifyFileName = requestFileName;
66 | }
67 | }
68 | console.log(modifyFileName);
69 | // 文件热封装
70 | const protoablePath = process.env.PORTABLE_EXECUTABLE_DIR;
71 | const modPath = protoablePath
72 | ? protoablePath + '/mods'
73 | : path.join(userDataPath, 'mods');
74 | if (!fs.existsSync(modPath)) {
75 | fs.mkdirSync(modPath);
76 | }
77 | const modifyFilePath = path.join(modPath, modifyFileName);
78 | if (modifyFileName !== '' && fs.existsSync(modifyFilePath)) {
79 | log.info(requestFileName, 'modify by Server');
80 | // AFT和PNG文件直接回传
81 | if (
82 | modifyFileName === 'MainFont.aft' ||
83 | path.extname(modifyFileName) === 'png'
84 | ) {
85 | fs.createReadStream(modifyFilePath).pipe(res);
86 | return;
87 | }
88 | // 其他文件
89 | let result: AL;
90 | options.gzip = true;
91 | request(options, (err, response, body) => {
92 | result = parse(body);
93 | // 这边也需要添加一个任务队列,不然会爆炸
94 | const packaged = result.Package(modifyFilePath);
95 | // fs.writeFile('./test/' + requestFileName, packaged);
96 | res.send(packaged);
97 | // res.send(body);
98 | });
99 | } else {
100 | request(options, (err, res, body) => {
101 | if (body === undefined) {
102 | console.log('Error on ' + req.path);
103 | }
104 | }).pipe(res);
105 | }
106 | });
107 | const server = http.createServer(app);
108 | server.on('error', (e) => {
109 | console.log(e);
110 | });
111 | server.listen('19980', () => {
112 | this.Port = server.address()['port'];
113 | console.log('listen at ' + this.Port);
114 | });
115 | }
116 | setFileList(fileList) {
117 | this.FileList = fileList;
118 | }
119 | setFontPath(path: string) {
120 | mainFontPath = path;
121 | }
122 | setProxy(enable, isSocks5, host, port) {
123 | this.ProxyEnable = enable;
124 | this.ProxyIsSocks5 = isSocks5;
125 | if (enable) {
126 | this.ProxyHost = host;
127 | this.ProxyPort = port;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/app/core/pluginHelper.ts:
--------------------------------------------------------------------------------
1 | import { ElectronService } from './electron.service';
2 | import { Plugin, PluginService } from './plugin.service';
3 | import { WebContents, webContents } from 'electron';
4 | import { GameService } from './game.service';
5 | import * as crypto from 'crypto';
6 | import * as fs from 'fs';
7 | import * as path from 'path';
8 | import { GlobalStatusService } from '../global/globalStatus.service';
9 | import { AigisGameDataService } from '../gameData/aigis/aigis.service';
10 | import { AigisStatisticsService } from '../gameData/aigis/statistics.service';
11 |
12 | const md5 = crypto.createHash('md5');
13 | export class PluginHelper {
14 | constructor(
15 | public electronService: ElectronService,
16 | public gameService: GameService,
17 | public plugin: Plugin,
18 | public globalStatusService: GlobalStatusService,
19 | public aigisGameDataService: AigisGameDataService,
20 | public aigisStatisticsService: AigisStatisticsService,
21 | public pluginService: PluginService
22 | ) { }
23 | loadEmbedPage(type: string, path: string, width: number) {
24 | this.pluginService.activeEmbedPlugin(type, path, { width: width }, this.plugin);
25 | }
26 | setExtraHeight(height: number) {
27 | this.globalStatusService.GlobalStatusStore.Get('ExtraHeight').Dispatch(height);
28 | }
29 | setExtraWidth(width: number) {
30 | this.globalStatusService.GlobalStatusStore.Get('ExtraWidth').Dispatch(width);
31 | }
32 | on(event: string, callback: (event?: any) => void) {
33 | // EventList
34 | this.plugin.backGroundObverser[event] = callback;
35 | }
36 | onMessage(callback: (msg: any, sendResponse?: any) => void) {
37 | const asyncChannel = `${this.plugin.id}`;
38 | this.electronService.ipcMain.on(asyncChannel, (event, msg) => {
39 | const salt = msg.salt;
40 | const message = msg.message;
41 | const sendResponse = message => {
42 | if (typeof message === 'object') {
43 | message = JSON.parse(JSON.stringify(message));
44 | }
45 | const channel = `${this.plugin.id}-${salt}`;
46 | event.sender.send(channel, message);
47 | };
48 | callback(message, sendResponse);
49 | });
50 |
51 | const syncChannel = `${this.plugin.id}-sync`;
52 | this.electronService.ipcMain.on(syncChannel, (event, msg) => {
53 | event.returnValue = callback(msg);
54 | });
55 | }
56 | sendMessage(message, callback?: (message: any) => void) {
57 | const channel = `${this.plugin.id}`;
58 | const salt = crypto
59 | .createHash('md5')
60 | .update(Math.random().toString())
61 | .digest('hex');
62 | const obj = {
63 | salt: salt,
64 | message: message
65 | };
66 | const replyChannel = `${this.plugin.id}-${salt}`;
67 | this.electronService.ipcMain.once(replyChannel, (event, message) => {
68 | if (typeof callback === 'function') {
69 | callback(message);
70 | } else {
71 | console.log(callback);
72 | }
73 | });
74 | if (this.plugin.activedWindow) {
75 | try {
76 | this.plugin.activedWindow.WebContent.send(channel, obj);
77 | } catch (err) {
78 | console.log(err, message);
79 | }
80 | }
81 | if (this.gameService.WebView) {
82 | // this.gameService.WebView.send(channel, obj);
83 | }
84 | }
85 | insertCssFileToGame(path, dirname?) {
86 | if (dirname) {
87 | dirname = dirname.replace(/\\/g, '/');
88 | } else {
89 | dirname = '';
90 | }
91 | let css = fs.readFileSync(path, 'utf8');
92 | css = css.replace(/\s{2,10}/g, ' ').trim();
93 | css = css.replace(/chrome-extension:\/\/__MSG_@@extension_id__/g, dirname);
94 | this.gameService.WebView.insertCSS(css);
95 | }
96 | insertCssToGame(css) {
97 | this.gameService.WebView.insertCSS(css);
98 | }
99 | createWindow(file, option) {
100 | const remote = require('@electron/remote');
101 | const BrowserWindow = remote.BrowserWindow;
102 | const currentWindow = require('@electron/remote').getCurrentWindow();
103 | const path = require('path');
104 | const url = require('url').format({
105 | protocol: 'file',
106 | slashes: true,
107 | pathname: file
108 | });
109 | if (!option['webPreferences']) {
110 | option['webPreferences'] = {};
111 | }
112 | option['webPreferences']['preload'] = path.join(__dirname, './assets/js/pluginWindowPreload.js');
113 | option.parent = currentWindow;
114 | const win = new BrowserWindow(option);
115 | win.loadURL(url);
116 | win.webContents.on('dom-ready', (event: any) => {
117 | event.sender.send('plugin-info', this.plugin);
118 | });
119 | return win;
120 | }
121 | loadURL(url) {
122 | this.gameService.WebView.loadURL(url);
123 | }
124 | }
125 |
126 | export class PluginHelperForRender {
127 | constructor() { }
128 | }
129 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aigisplayer",
3 | "version": "2.4.2",
4 | "description": "Toolkit for Aigis",
5 | "productName": "AigisPlayer",
6 | "homepage": "https://github.com/DogReactor/AigisPlayer",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/DogReactor/AigisPlayer.git"
10 | },
11 | "author": {
12 | "name": "Yukimir",
13 | "email": "luodadi.huazhu@gmail.com"
14 | },
15 | "main": "main.js",
16 | "private": true,
17 | "scripts": {
18 | "pinstall": "electron-builder install-app-deps",
19 | "ng": "ng",
20 | "start": "npm-run-all -p ng:serve electron:serve",
21 | "build": "rimraf dist && ng build --prod && npm run electron:build-tsc && npm run electron:build",
22 | "build:dev": "npm run build -- -c dev",
23 | "build:prod": "npm run build -- -c production",
24 | "ng:serve": "ng serve",
25 | "ng:serve:web": "ng serve -c web -o",
26 | "electron:serve-tsc": "tsc -p tsconfig-serve.json",
27 | "electron:build-tsc": "tsc -p tsconfig-build.json && copyfiles assets/**/*.* dist",
28 | "electron:build": "copyfiles package.json dist && cd dist && npm install --omit=dev && electron-rebuild",
29 | "electron:serve": "wait-on http-get://localhost:4200/ && npm run electron:serve-tsc && electron . --serve",
30 | "electron:local": "npm run build:prod && cd dist && electron .",
31 | "electron:linux": "npm run build:prod && electron-builder build --linux",
32 | "electron:windows": "npm run build:prod && electron-builder build --windows",
33 | "electron:mac": "npm run build:prod && electron-builder build --mac",
34 | "electron:publish": "npm run build:prod && electron-builder build --publish always",
35 | "test": "ng test",
36 | "e2e": "npm run build:prod && mocha --timeout 300000 --require ts-node/register e2e/**/*.spec.ts",
37 | "version": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
38 | "lint": "ng lint"
39 | },
40 | "dependencies": {
41 | "@electron/remote": "^2.1.2",
42 | "aigis-fuel": "git+https://github.com/Yukimir/AigisFuel.git",
43 | "compare-versions": "^3.4.0",
44 | "electron-log": "^2.2.17",
45 | "electron-store": "^8.1.0",
46 | "electron-updater": "4.2.0",
47 | "gifuct-js": "^1.0.0",
48 | "request": "^2.88.2",
49 | "type-fest": "^3.12.0",
50 | "unzipper": "^0.9.4"
51 | },
52 | "devDependencies": {
53 | "element-angular": "^0.7.6",
54 | "@angular-builders/custom-webpack": "8.2.0",
55 | "@angular-devkit/build-angular": "0.803.6",
56 | "@angular-eslint/builder": "0.0.1-alpha.17",
57 | "@angular/animations": "8.2.14",
58 | "@angular/cli": "8.3.6",
59 | "@angular/common": "8.2.12",
60 | "@angular/compiler": "8.2.12",
61 | "@angular/compiler-cli": "8.2.12",
62 | "@angular/core": "8.2.12",
63 | "@angular/forms": "8.2.12",
64 | "@angular/language-service": "8.2.12",
65 | "@angular/platform-browser": "8.2.12",
66 | "@angular/platform-browser-dynamic": "8.2.12",
67 | "@angular/router": "8.2.12",
68 | "@ngx-translate/core": "11.0.1",
69 | "@ngx-translate/http-loader": "4.0.0",
70 | "@types/express": "^4.16.0",
71 | "@types/jasmine": "3.3.16",
72 | "@types/jasminewd2": "2.0.6",
73 | "@types/mocha": "5.2.7",
74 | "@types/node": "^13.0.0",
75 | "@types/request": "^2.48.1",
76 | "@types/rx": "^4.1.1",
77 | "@types/unzip": "^0.1.1",
78 | "@typescript-eslint/eslint-plugin": "2.8.0",
79 | "@typescript-eslint/parser": "2.8.0",
80 | "chai": "4.2.0",
81 | "codelyzer": "5.1.0",
82 | "conventional-changelog-cli": "2.0.25",
83 | "copyfiles": "^2.1.1",
84 | "core-js": "3.1.4",
85 | "cz-conventional-changelog": "^2.1.0",
86 | "electron": "34.1.1",
87 | "electron-builder": "21.2.0",
88 | "electron-reload": "1.5.0",
89 | "electron-rebuild": "^2.3.5",
90 | "eslint": "6.6.0",
91 | "eslint-plugin-import": "2.18.2",
92 | "husky": "^3.0.1",
93 | "jasmine-core": "3.4.0",
94 | "jasmine-spec-reporter": "4.2.1",
95 | "karma": "4.2.0",
96 | "karma-coverage-istanbul-reporter": "2.1.0",
97 | "karma-electron": "6.3.0",
98 | "karma-jasmine": "2.0.1",
99 | "karma-jasmine-html-reporter": "1.4.2",
100 | "mkdirp": "0.5.1",
101 | "mocha": "6.2.0",
102 | "npm-run-all": "4.1.5",
103 | "prettier": "^1.18.2",
104 | "rimraf": "^2.7.1",
105 | "rxjs": "6.5.3",
106 | "rxjs-compat": "^6.5.4",
107 | "spectron": "9.0.0",
108 | "ts-node": "8.3.0",
109 | "tslint": "5.11.0",
110 | "typescript": "<3.6.0",
111 | "wait-on": "3.3.0",
112 | "webdriver-manager": "12.1.5",
113 | "zone.js": "0.8.29"
114 | },
115 | "license": "SEE LICENSE IN LICENSE.md",
116 | "config": {
117 | "commitizen": {
118 | "path": "./node_modules/cz-conventional-changelog"
119 | }
120 | },
121 | "lint-staged": {
122 | "src/**/*": [
123 | "prettier",
124 | "lint",
125 | "git add"
126 | ]
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AigisPlayer
2 |
3 | 千年戦争 Aigis On Desktop
4 |
5 | ## Install
6 |
7 | git clone https://github.com/Yukimir/AigisPlayer
8 | cd AigisPlayer
9 | npm install
10 | npm start
11 |
12 | ## Plugin
13 |
14 | ### 运行方式
15 |
16 | 1、独立窗口:Chrome 称之为 popup,在 AP2 中通过右下角插件菜单呼出。运行于独立进程中。
17 |
18 | 2、后台脚本:在程序启动时自动运行。同主窗口运行于同一进程中。
19 |
20 | 3、注入脚本:指定游戏的页面读取时注入的脚本。与游戏运行于同一进程中。
21 |
22 | ### 插件文件结构
23 |
24 | 插件存放于 plugins 目录下的目录中。如"dropShow"插件存放于 plugins/dropShow 目录中
25 |
26 | 除了必须有 manifest.json 文件来说明插件信息以外,其他可以随意。
27 |
28 | ### manifest.json 范例
29 |
30 | ```typescript
31 | // plugins/dropshow/manifest.json
32 |
33 | {
34 | "pluginName":"dropShow",
35 | "author":"Yukimir",
36 | "version":"0.0.1",
37 | "description":"掉落查看",
38 | "entry":"src/popup/popup.html", // 独立窗口的入口html文件
39 | "background":"background.js", // 后台脚本的入口点
40 | "game":["aigis"], // 注入脚本要注入的游戏
41 | "inject":"inject.js", // 注入脚本的入口点
42 | "windowOption":{ // 独立窗口的窗口设置,详见https://electronjs.org/docs/api/browser-window
43 | "width":288,
44 | "height":340,
45 | "title":"掉落查看",
46 | "minimizable":true,
47 | "maximizable":false,
48 | "transparent":true
49 | }
50 | }
51 | ```
52 |
53 | ### 脚本要求
54 |
55 | 后台脚本和注入脚本都必须使用 module.exports 的方式导出一个包含 run(pluginHelper:PluginHelper)的对象,例如
56 |
57 | ```javascript
58 | // background.js
59 |
60 | module.exports = {
61 | run: function(pluginHelper) {
62 | console.log(pluginHelper);
63 | }
64 | };
65 | ```
66 |
67 | 独立窗口必须提供一个 run()函数作为入口点,比如
68 |
69 | ```html
70 | // index.html
71 |
72 |
73 |
74 |
79 |
80 |
81 | ```
82 |
83 | ### PluginHelper
84 |
85 | PluginHelper 分为两个版本
86 |
87 | #### 后台脚本用 PluginHelper:
88 |
89 | ##### onMessage(callback: (msg: any, sendResponse?: (msg: any) => void) => void)
90 |
91 | 用来监听注入脚本和独立窗口发送的同步和异步消息
92 |
93 | 同步消息可以直接使用 return 来回复消息
94 | 异步消息可以用 sendResponse 方法来回复消息
95 |
96 | ※sendResponse 只能使用一次
97 |
98 | ##### sendMessage(message:any, callback?: (message: any) => void)
99 |
100 | 用来向注入脚本及独立窗口发送信息,提供的 callback 参数用来接收回复。
101 |
102 | ※callback 只会被触发一次
103 |
104 | ##### insertCssFileToGame(path:string)
105 |
106 | 用来向游戏注入 CSS 文件,提供的 path 为绝对路径(可以使用\_\_dirname 来获取)
107 |
108 | ※你应该在 dom-ready 之后执行此方法
109 |
110 | ##### insertCssToGame(css:string)
111 |
112 | 用来向游戏注入 CSS 规则
113 |
114 | ※你应该在 dom-ready 之后执行此方法
115 |
116 | ##### electronService: ElectronService/gameService: GameService
117 |
118 | 参考 app/core/electron.service.ts | app/core/game.service.ts
119 |
120 | ##### plugin:Plugin
121 |
122 | 参考 app/core/plugin.service.ts::Plugin
123 |
124 | #### 注入/独立窗口用 PluginHelper
125 |
126 | ##### onMessage(callback: (msg: any, sendResponse?: (msg: any) => void) => void)
127 |
128 | 用来监听后台脚本发送的消息
129 |
130 | 可以用 sendResponse 方法来回复消息
131 |
132 | ##### sendMessage(message:any, callback?: (message: any) => void)
133 |
134 | 用来向后台脚本发送消息,提供的 callback 参数用来接收回复。
135 |
136 | ##### sendMessageSync(message: any) => any
137 |
138 | 用来向后台脚本发送同步消息,返回值为回复
139 |
140 | ##### createWindow(file: string, option: WindowOption):BrowserWindow
141 |
142 | file 为绝对路径,option 为窗口选项,参考https://electronjs.org/docs/api/browser-window
143 |
144 | 创建的新窗口以当前窗口为父窗口,并需要提供 run 函数来获得 pluginHelper
145 |
146 | ### 生命周期
147 |
148 | 后台脚本,注入脚本在 dom-ready 之前执行 run(pluginHelper)函数
149 |
150 | 独立窗口在 dom-ready 之后执行 run(pluginHelper)函数
151 |
152 | 因为 electron 本身的缺陷,在 dom-ready 事件之前,有概率会无法接收到异步的 message。所以在 dom-ready 事件之前请尽量用 sendMessageSync 来进行通信。
153 |
154 | ### 通信示例
155 |
156 | ```javascript
157 | // background.js
158 |
159 | pluginHelper.onMessage((msg, sendResponse) => {
160 | console.log(msg); // print: ping from inject/popup
161 | if (sendResponse) sendResponse('pong from back async');
162 | //asnyc
163 | else return 'pong from back sync'; //sync
164 | });
165 | pluginHelper.sendMessage('ping from back', msg => {
166 | console.log(msg); // print: pong from inject/popup
167 | });
168 |
169 | // inject.js / index.html
170 |
171 | pluginHelper.onMessage((msg, sendResponse) => {
172 | console.log(msg); // print: ping from back
173 | sendResponse('pong from inject/popup');
174 | });
175 |
176 | pluginHelper.sendMessage('ping from inject/popup', msg => {
177 | console.log(msg); // print: pong from back async
178 | });
179 |
180 | console.log(pluginHelper.sendMessageSync('ping from inject/popup')); // print: pong from back sync
181 | ```
182 |
183 | ### 游戏数据
184 |
185 | 目前暂时只支持 千年战争 aigis
186 | 或许会支持御城(等我哪天入坑的吧 x)
187 |
188 | 后台脚本通过调用 pluginHelper.aigisGameDataService.subscribe 方法来订阅游戏通信数据和静态资源文件
189 |
190 | ```javascript
191 | // background.js
192 |
193 | module.exports = {
194 | run: pluginHelper => {
195 | // 订阅通信数据
196 | // 事件名参考 app/gameData/aigis/EventList.ts
197 | pluginHelper.aigisGameDataService.subscribe('allcards-info', (url, data) => {
198 | // ....
199 | });
200 |
201 | // 订阅静态资源
202 | // 可以订阅fileList事件来获取文件列表
203 | pluginHelper.aigisGameDataService.subscribe('NameText.atb', (url, data) => {
204 | // ....
205 | });
206 | }
207 | };
208 | ```
209 |
--------------------------------------------------------------------------------
/src/app/gameData/aigis/aigis.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Translated from http://millenniumwaraigis.wikia.com/wiki/User_blog:Lzlis/Interpreting_POST_responses
3 | */
4 |
5 | import { Injectable } from '@angular/core';
6 | import { DebuggerService } from '../debugger.service';
7 | import { Decoder } from './decode';
8 | import { Decompress } from './decompress';
9 | import { parseAL } from 'aigis-fuel';
10 | import { URL } from 'url';
11 | import { Xml2json } from './xml2json';
12 | import { Event } from './EventList';
13 | import { Base64 } from './util';
14 | import { ElectronService } from '../../core/electron.service';
15 |
16 | class AssetsCollector {
17 | private roster: Map<(file) => boolean, (url, response, request?) => void> = new Map();
18 | // 一个url可能对应多个文件
19 | public EigenUrls: Map> = new Map();
20 | constructor() { }
21 | register(filter: (file) => boolean, callback: (url, response, request?) => void) {
22 | this.roster.set(filter, callback);
23 | }
24 | checkUrl(label: string, url: string) {
25 | this.roster.forEach((callback, filter) => {
26 | if (filter(label)) {
27 | if (this.EigenUrls.has(url)) {
28 | this.EigenUrls.get(url).push(label);
29 | } else {
30 | this.EigenUrls.set(url, [label]);
31 | }
32 | }
33 | });
34 | }
35 | sendCollection(url: string, data: any) {
36 | this.EigenUrls.get(url).forEach(key => {
37 | this.roster.forEach((callback, filter) => {
38 | if (filter(key)) {
39 | callback(url, { Label: key, Data: data });
40 | }
41 | });
42 | });
43 | }
44 | }
45 |
46 | @Injectable()
47 | export class AigisGameDataService {
48 | private subscription: Map void>> = new Map();
49 | private assetsRoster: Map = new Map();
50 | private assetsCollector: AssetsCollector = new AssetsCollector();
51 | constructor(private debuggerService: DebuggerService, private electronService: ElectronService) {
52 | debuggerService.Subscribe(
53 | {
54 | url: ['://millennium-war.net/', '://all.millennium-war.net/'],
55 | method: 'POST',
56 | request: true
57 | },
58 | (url, res, req) => {
59 | const u = new URL(url);
60 | const path = u.pathname.replace('/', '');
61 | const channel = Event[path];
62 | if (channel && this.subscription.has(channel)) {
63 | Promise.all([this.parseData(res), this.parseData(req)]).then(
64 | ([response, request]) => {
65 | return this.subscription.get(channel).forEach(v => v(url, response, request))
66 | },
67 | err => {
68 | console.log('err in ', channel, res, req);
69 | throw err;
70 | }
71 | );
72 | }
73 | }
74 | );
75 |
76 | debuggerService.Subscribe(
77 | {
78 | url: ['://drc1bk94f7rq8.cloudfront.net/'],
79 | method: 'GET',
80 | request: false
81 | },
82 | (url: string, response: Buffer) => {
83 | if (
84 | url.indexOf('/2iofz514jeks1y44k7al2ostm43xj085') !== -1 ||
85 | url.indexOf('/1fp32igvpoxnb521p9dqypak5cal0xv0') !== -1
86 | ) {
87 | console.debug('Get Aigis FileList');
88 | const allFileList = Decoder.DecodeList(response);
89 | const reverseList = {};
90 | allFileList.forEach((v, k) => {
91 | // fileList里似乎有个无名key
92 | if (k) {
93 | if (this.subscription.has(k)) {
94 | this.assetsRoster.set(v, k);
95 | }
96 | reverseList[v.replace('https://drc1bk94f7rq8.cloudfront.net', '')] = k;
97 | this.assetsCollector.checkUrl(k, v);
98 | }
99 | });
100 | electronService.ipcRenderer.send('fileList', reverseList);
101 | if (this.subscription.has('fileList')) {
102 | this.subscription.get('fileList').forEach(v => {
103 | v(url, allFileList);
104 | });
105 | }
106 | } else if (this.assetsRoster.has(url) || this.assetsCollector.EigenUrls.has(url)) {
107 | const buffer = response;
108 | const data = parseAL(buffer) || buffer;
109 | if (this.assetsRoster.has(url)) {
110 | const channel = this.assetsRoster.get(url);
111 | this.subscription.get(channel).forEach(v => {
112 | v(url, data);
113 | });
114 | }
115 | if (this.assetsCollector.EigenUrls.has(url)) {
116 | this.assetsCollector.sendCollection(url, data);
117 | }
118 | }
119 | }
120 | );
121 | }
122 |
123 | subscribe(channel, callback: (url: string, response: any, request?: any) => void, request?: boolean) {
124 | if (typeof channel === 'string') {
125 | if (this.subscription.has(channel)) {
126 | this.subscription.get(channel).push(callback);
127 | } else {
128 | this.subscription.set(channel, [callback]);
129 | }
130 | } else if (typeof channel === 'function') {
131 | this.assetsCollector.register(channel, callback);
132 | }
133 | }
134 |
135 | async parseData(buffer: Buffer) {
136 | try {
137 | let decoded = Decoder.DecodeXml(buffer);
138 | let data;
139 | if (decoded) {
140 | const decompressed = Decompress(decoded);
141 | decoded = decompressed ? decompressed : decoded;
142 | const body_str = [];
143 | for (let i = 0; i < decoded.byteLength; i++) {
144 | body_str.push(String.fromCharCode(decoded[i]));
145 | }
146 | data = body_str.join('');
147 | } else {
148 | data = buffer.toString();
149 | }
150 | data = Xml2json(data);
151 | data = data['DA'] || data;
152 | return data;
153 | } catch (err) {
154 | throw err;
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "angular-electron": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-builders/custom-webpack:browser",
13 | "options": {
14 | "outputPath": "dist/ng",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets",
21 | "src/favicon.ico",
22 | "src/favicon.png",
23 | "src/favicon.icns",
24 | "src/favicon.256x256.png",
25 | "src/favicon.512x512.png"
26 | ],
27 | "styles": [
28 | "src/styles.scss"
29 | ],
30 | "scripts": [],
31 | "customWebpackConfig": {
32 | "path": "./angular.webpack.js"
33 | }
34 | },
35 | "configurations": {
36 | "dev": {
37 | "optimization": false,
38 | "outputHashing": "all",
39 | "sourceMap": true,
40 | "extractCss": true,
41 | "namedChunks": false,
42 | "aot": false,
43 | "extractLicenses": true,
44 | "vendorChunk": false,
45 | "buildOptimizer": false,
46 | "fileReplacements": [
47 | {
48 | "replace": "src/environments/environment.ts",
49 | "with": "src/environments/environment.dev.ts"
50 | }
51 | ]
52 | },
53 | "web": {
54 | "optimization": false,
55 | "outputHashing": "all",
56 | "sourceMap": true,
57 | "extractCss": true,
58 | "namedChunks": false,
59 | "aot": false,
60 | "extractLicenses": true,
61 | "vendorChunk": false,
62 | "buildOptimizer": false,
63 | "fileReplacements": [
64 | {
65 | "replace": "src/environments/environment.ts",
66 | "with": "src/environments/environment.web.ts"
67 | }
68 | ]
69 | },
70 | "production": {
71 | "optimization": true,
72 | "outputHashing": "all",
73 | "sourceMap": false,
74 | "extractCss": true,
75 | "namedChunks": false,
76 | "aot": true,
77 | "extractLicenses": true,
78 | "vendorChunk": false,
79 | "buildOptimizer": true,
80 | "fileReplacements": [
81 | {
82 | "replace": "src/environments/environment.ts",
83 | "with": "src/environments/environment.prod.ts"
84 | }
85 | ]
86 | }
87 | }
88 | },
89 | "serve": {
90 | "builder": "@angular-builders/custom-webpack:dev-server",
91 | "options": {
92 | "browserTarget": "angular-electron:build"
93 | },
94 | "configurations": {
95 | "dev": {
96 | "browserTarget": "angular-electron:build:dev"
97 | },
98 | "web": {
99 | "browserTarget": "angular-electron:build:web"
100 | },
101 | "production": {
102 | "browserTarget": "angular-electron:build:production"
103 | }
104 | }
105 | },
106 | "extract-i18n": {
107 | "builder": "@angular-devkit/build-angular:extract-i18n",
108 | "options": {
109 | "browserTarget": "angular-electron:build"
110 | }
111 | },
112 | "test": {
113 | "builder": "@angular-builders/custom-webpack:karma",
114 | "options": {
115 | "main": "src/test.ts",
116 | "polyfills": "src/polyfills-test.ts",
117 | "tsConfig": "src/tsconfig.spec.json",
118 | "karmaConfig": "src/karma.conf.js",
119 | "scripts": [],
120 | "styles": [
121 | "src/styles.scss"
122 | ],
123 | "assets": [
124 | "src/assets",
125 | "src/favicon.ico",
126 | "src/favicon.png",
127 | "src/favicon.icns",
128 | "src/favicon.256x256.png",
129 | "src/favicon.512x512.png"
130 | ],
131 | "customWebpackConfig": {
132 | "path": "./angular.webpack.js",
133 | "target": "electron-renderer"
134 | }
135 | }
136 | },
137 | "lint": {
138 | "builder": "@angular-eslint/builder:lint",
139 | "options": {
140 | "eslintConfig": "src/eslintrc.config.json",
141 | "tsConfig": [
142 | "src/tsconfig.app.json",
143 | "src/tsconfig.spec.json"
144 | ],
145 | "exclude": [
146 | "**/node_modules/**"
147 | ]
148 | }
149 | }
150 | }
151 | },
152 | "angular-electron-e2e": {
153 | "root": "e2e",
154 | "projectType": "application",
155 | "architect": {
156 | "lint": {
157 | "builder": "@angular-eslint/builder:lint",
158 | "options": {
159 | "eslintConfig": "e2e/eslintrc.e2e.json",
160 | "tsConfig": [
161 | "e2e/tsconfig.e2e.json"
162 | ],
163 | "exclude": [
164 | "**/node_modules/**"
165 | ]
166 | }
167 | }
168 | }
169 | }
170 | },
171 | "defaultProject": "angular-electron",
172 | "schematics": {
173 | "@schematics/angular:component": {
174 | "prefix": "app",
175 | "styleext": "scss"
176 | },
177 | "@schematics/angular:directive": {
178 | "prefix": "app"
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/app/uiFramework/modules/game/game.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, AfterViewInit, OnDestroy, OnInit } from '@angular/core';
2 | import { TranslateService } from '@ngx-translate/core';
3 | import { GameService } from '../../../core/game.service';
4 | import { GlobalSettingService, Account } from '../../../global/globalSetting.service';
5 | import { GlobalStatusService } from '../../../global/globalStatus.service';
6 | import { ElMessageService } from 'element-angular';
7 | import * as Rx from 'rxjs';
8 | import { WebviewTag, WebContents, webContents } from 'electron';
9 | import { ElectronService } from '../../../core/electron.service';
10 | import { PluginService } from '../../../core/plugin.service';
11 | import { GameModel } from '../../../core/game.model';
12 | import { LogService } from '../../../core/log.service';
13 | import { HotkeyService } from '../../../core/hotkey.service';
14 |
15 | @Component({
16 | selector: 'app-game',
17 | templateUrl: './game.component.html',
18 | styleUrls: ['./game.component.scss']
19 | })
20 | export class GameComponent implements AfterViewInit, OnDestroy, OnInit {
21 | private gameView: WebviewTag = null;
22 | private zoom = 100;
23 | private subscriptionList: Rx.Subscription[] = [];
24 | private dirname = '';
25 | private preloadScriptPath = '';
26 | constructor(
27 | private gameService: GameService,
28 | private globalSettingService: GlobalSettingService,
29 | private globalStatusService: GlobalStatusService,
30 | private message: ElMessageService,
31 | private translateService: TranslateService,
32 | private electronService: ElectronService,
33 | private logService: LogService,
34 | private hotkeyService: HotkeyService,
35 | private pluginService: PluginService
36 | ) {
37 | this.subscriptionList.push(
38 | this.globalStatusService.GlobalStatusStore.Get('Zoom').Subscribe(v => {
39 | this.zoom = v;
40 | if (this.gameView) {
41 | if (this.gameView.setZoomFactor === undefined) {
42 | return;
43 | }
44 | this.gameView.setZoomFactor(this.zoom / 100);
45 | }
46 | })
47 | );
48 | }
49 | ngOnInit() {
50 | this.dirname = this.electronService.APP['dirname'];
51 | this.preloadScriptPath = `file://${this.dirname}/assets/js/inject.js`;
52 | }
53 | ngAfterViewInit() {
54 | this.gameView = document.getElementById('gameView');
55 | // this.gameView.preload = `file://${this.dirname}/assets/js/inject.js`;
56 | this.gameService.WebView = this.gameView;
57 | const webview = this.gameView;
58 |
59 | webview.addEventListener('load-commit', event => {
60 | if (event.isMainFrame) {
61 | this.logService.Url = event.url;
62 | }
63 | });
64 | webview.addEventListener('did-frame-navigate', event => {
65 | if (this.gameService.webContents) {
66 | const url = (event as any).url as string;
67 | const routingID = (event as any).frameRoutingId;
68 | if (
69 | url.indexOf('.mimolette.co.jp/ps01/game_webgl_player.html') !== -1 ||
70 | url.indexOf('.funyours.co.jp/ps01/game_webgl_player.html') !== -1 ||
71 | url.indexOf('asset.wander.games') !== -1
72 | ) {
73 | this.gameService.webContents.send('frame', routingID);
74 | }
75 | if (
76 | url === "https://drc1bk94f7rq8.cloudfront.net/00/html/aigis.html" ||
77 | url.indexOf('//assets.shiropro-re.net/html/Oshiro.html') !== -1
78 | ) {
79 | console.log(routingID);
80 | this.gameService.frameID = routingID;
81 | this.gameService.webContents.sendToFrame(routingID, 'aigis-tick', this.gameService.SlowTick);
82 | this.gameService.webContents.send('aigis-frame', routingID);
83 | }
84 | }
85 | });
86 | webview.addEventListener('dom-ready', () => {
87 | const mute = this.globalStatusService.GlobalStatusStore.Get('Mute').Value;
88 | webview.setAudioMuted(mute);
89 | const CurrentGame = this.globalStatusService.GlobalStatusStore.Get('CurrentGame').Value;
90 | // 第一次打开时启动默认游戏
91 | if (!this.gameView.canGoBack()) {
92 | this.gameService.LoadGame(CurrentGame);
93 | }
94 | this.gameView.setZoomFactor(this.zoom / 100);
95 |
96 | if (this.electronService.serve) {
97 | // 打开开发者工具
98 | webview.openDevTools();
99 | }
100 | // 碧蓝删去滑动条
101 | if (CurrentGame.Spec === 'granblue') {
102 | webview.send('catch', CurrentGame.Spec);
103 | webview.insertCSS('::-webkit-scrollbar{display:none!important}');
104 | }
105 |
106 | // 通知页面进行调整
107 | if (
108 | webview.getURL().indexOf('app_id') !== -1 ||
109 | webview.getURL().indexOf('/play/') !== -1 ||
110 | webview.getURL().indexOf('/game/') !== -1 ||
111 | webview.getURL().indexOf('game_dmm.php') !== -1
112 | ) {
113 | webview.send('catch', CurrentGame.Spec);
114 | }
115 |
116 | // 自动输入用户名密码
117 | if (
118 | webview.getURL().indexOf('dmm') !== -1 &&
119 | webview.getURL().indexOf('login') !== -1 &&
120 | webview.getURL().indexOf('logout') === -1
121 | ) {
122 | // 从globalSetting中获取账号密码
123 | const username = this.globalStatusService.GlobalStatusStore.Get('SelectedAccount').Value;
124 | const account = this.globalSettingService.FindAccount(username);
125 | if (account) {
126 | webview.send('login', {
127 | username: account.Username,
128 | password: account.Password
129 | });
130 | }
131 | }
132 | });
133 | webview.addEventListener('did-finish-load', () => {
134 | this.gameView.setZoomFactor(this.zoom / 100);
135 | });
136 | webview.addEventListener('did-fail-load', event => {
137 | if (event.errorDescription === '' || event.errorDescription === '' || event.isMainFrame === false) {
138 | return;
139 | }
140 | this.translateService.get('MESSAGE.PAGE-DIDNOT-LOAD').subscribe(res => this.message['warning'](res));
141 | });
142 |
143 | // webview.addEventListener('new-window', e => {
144 | // const option = e.options;
145 | // option['height'] = 640;
146 | // option['width'] = 1100;
147 | // option['autoHideMenuBar'] = true;
148 | // option['webPreferences']['session'] = this.gameService.webContents.session;
149 | // this.electronService.CreateBrowserWindow(e.url, option);
150 | // });
151 | }
152 | ngOnDestroy() {
153 | for (let i = 0; i < this.subscriptionList.length; i++) {
154 | this.subscriptionList[i].unsubscribe();
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/assets/js/fixPage.js:
--------------------------------------------------------------------------------
1 | // fuck confirm
2 | // TODO: 只针对特定的frame有效
3 | const { ipcRenderer, remote } = require('electron');
4 | window['confirm'] = () => {
5 | return false;
6 | };
7 |
8 | var delay = (time) => {
9 | return new Promise((reslove, reject) => {
10 | setTimeout(() => {
11 | reslove();
12 | }, time);
13 | })
14 | }
15 |
16 | var getWebFrame = async () => {
17 | let frame;
18 | while (!frame) {
19 | frame = document.getElementById('game_frame');
20 | console.log(frame);
21 | await delay(10);
22 | }
23 | return frame;
24 | }
25 |
26 | ipcRenderer.on('catch', async (event, message) => {
27 | // GBF
28 | if (window.location.href.indexOf('game.granbluefantasy.jp') !== -1) {
29 | require('./gbf/fixSlideBar');
30 | return;
31 | }
32 | if (message === 'bravegirl') {
33 | var bottomFrame = document.getElementById('bottomFrame');
34 | if (bottomFrame) {
35 | bottomFrame.style.position = 'fixed';
36 | bottomFrame.style.top = '0px';
37 | bottomFrame.style.left = '-8px';
38 | bottomFrame.style.width = '1032px';
39 | bottomFrame.style.zIndex = '10001';
40 | }
41 | return;
42 | }
43 | var gameFrame = await getWebFrame();
44 | if (gameFrame === null) {
45 | console.error("game Frame not found");
46 | ipcRenderer.sendToHost('url', 'error');
47 | }
48 | else {
49 | gameFrame.style.position = 'fixed';
50 | document.body.style.overflow = 'hidden';
51 | gameFrame.style.zIndex = '10';
52 | if (message === 'kamihime') {
53 | gameFrame.style.top = '-28px';
54 | gameFrame.style.left = '-150px';
55 | }
56 | if (message === 'fkg') {
57 | gameFrame.style.top = '-6px';
58 | gameFrame.style.left = '-60px';
59 | }
60 | if (message === 'aigis' || message === 'oshiro') {
61 | gameFrame.style.top = '1px';
62 | gameFrame.style.left = '0';
63 | gameFrame.style.marginLeft = '-6px';
64 | }
65 | if (message === 'kankore') {
66 | gameFrame.style.top = '-16px';
67 | gameFrame.style.left = '0';
68 | }
69 | if (message === 'unitia') {
70 | gameFrame.style.top = '-52px';
71 | gameFrame.style.left = '0';
72 | }
73 | if (message === 'necro') {
74 | gameFrame.style.top = '-35px';
75 | gameFrame.style.left = '-22px';
76 | }
77 | if (message === 'kyoshin') {
78 | gameFrame.style.top = '-30px';
79 | gameFrame.style.left = '0px';
80 | }
81 | if (message === 'clover') {
82 | gameFrame.style.top = '-39px';
83 | gameFrame.style.left = '-109px'
84 | }
85 | if (message === 'misttraingirls') {
86 | gameFrame.style.top = '-15px';
87 | gameFrame.style.left = '0px';
88 | }
89 | if (message === 'angelic') {
90 | gameFrame.style.top = '-17px';
91 | gameFrame.style.left = '-11px';
92 | }
93 | if (message === 'minashigo') {
94 | gameFrame.style.top = '-16px';
95 | gameFrame.style.left = '-5px';
96 | }
97 | if (message === 'fruful') {
98 | gameFrame.style.top = '-7px';
99 | gameFrame.style.left = '-5px';
100 | }
101 | if (message === 'monster') {
102 | gameFrame.style.top = '-23px';
103 | gameFrame.style.left = '-137px';
104 | }
105 | }
106 | });
107 |
108 | ipcRenderer.on('change', (event, message) => {
109 | document.getElementById('content').style.position = 'fixed';
110 | ipcRenderer.sendToHost('changesuccess');
111 | });
112 |
113 | ipcRenderer.on('frame', (event, message) => {
114 | const { webFrame } = require('electron');
115 | const frame = webFrame.findFrameByRoutingId(message);
116 | frame.executeJavaScript('Module.TOTAL_MEMORY = 2000000000;0');
117 | });
118 |
119 | // TODO: 这俩抽到别的文件里去
120 | // 减速
121 | var slow = false;
122 |
123 | var fps = 15;
124 | var now;
125 | var then = Date.now();
126 | var interval = 1000 / fps;
127 | var delta;
128 | var draw;
129 | // 劫持reqeustAnimationFrame
130 | window['oldRequestAnimationFrame'] = window.requestAnimationFrame;
131 | function genSkipFrame(func) {
132 | return () => {
133 | window['oldRequestAnimationFrame'](func);
134 | };
135 | }
136 | function tick(func) {
137 | if (slow) {
138 | if (typeof (func) === "function") draw = func;
139 | now = Date.now();
140 | delta = now - then;
141 | if (delta > interval) {
142 | then = now - (delta % interval);
143 | var temp = draw;
144 | draw = null;
145 | window['oldRequestAnimationFrame'](temp);
146 | } else {
147 | window['oldRequestAnimationFrame'](tick);
148 | }
149 | } else {
150 | if (draw) func = draw;
151 | window['oldRequestAnimationFrame'](func);
152 | }
153 | }
154 | window.requestAnimationFrame = tick;
155 |
156 | // 暂停
157 | var pauseElement = document.createElement('div');
158 | pauseElement.style.position = 'fixed';
159 | pauseElement.style.top = '0';
160 | pauseElement.style.left = '0';
161 | pauseElement.style.zIndex = '10000';
162 | pauseElement.style.height = '100%';
163 | pauseElement.style.width = '100%';
164 | pauseElement.style.backgroundColor = 'black';
165 | pauseElement.style.opacity = '0.5';
166 | var pause = false;
167 | ipcRenderer.on('who-am-i', (event, message) => {
168 | console.log(location);
169 | })
170 | ipcRenderer.on('aigis-pause', (event, message) => {
171 | if (Module) {
172 | if (!pause) {
173 | Module.pauseMainLoop();
174 | document.body.appendChild(pauseElement);
175 | pause = true;
176 | } else {
177 | Module.resumeMainLoop();
178 | document.body.removeChild(pauseElement);
179 | pause = false;
180 | }
181 | }
182 | });
183 | ipcRenderer.on('aigis-tick', (event, message) => {
184 | slow = message;
185 | });
186 |
187 | var allMeta = {};
188 | window.process = undefined;
189 | window.require = undefined;
190 | if (location.hostname === 'r.kamihimeproject.net') {
191 | window.require = undefined;
192 | }
193 | // if (location.hostname === 'assets.millennium-war.net') {
194 | // // 暂时屏蔽全年龄版
195 | // if (parent.parent.location.pathname.indexOf('aigisc') !== -1) {
196 | // // 全年龄
197 | // } else {
198 | // // R18
199 | // window.onload = () => {
200 | // /////////////////////////////
201 | // /// 魔改Live2D或者别的一些什么东西
202 | // /////////////////////////////
203 | // require('./aigisAnimate');
204 | // window[r18Meta.create] = function(d, c, b) {
205 | // var e;
206 | // e = 0 < tTb.length ? tTb.pop() : new Image();
207 | // e.src = A8(b);
208 | // // 在这里通过e.src来处理testCanvas的相应显示
209 | // var result = loadAnimate(e.src);
210 | // // G8[d] = e;
211 | // G8[d] = result || e;
212 | // sTb[c] = e;
213 | // };
214 | // window[r18Meta.remove] = function(d) {
215 | // // 在这里处理终止targetCanvas动画的相应工作
216 | // img = sTb[d];
217 | // img.src = '';
218 | // img.width = 0;
219 | // img.height = 0;
220 | // delete sTb[d];
221 | // };
222 |
223 | // window[r18Meta.delete] = function(d) {
224 | // if (G8[d].isCustomedCanvas) {
225 | // stopAnimate();
226 | // }
227 | // delete G8[d];
228 | // };
229 | // };
230 | // }
231 | // }
232 |
--------------------------------------------------------------------------------
/requestHandler.ts:
--------------------------------------------------------------------------------
1 | import * as stream from 'stream';
2 | import { session, net, app, ipcMain, BrowserWindow } from 'electron';
3 | import * as url from 'url';
4 | import * as path from 'path';
5 | import * as fs from 'fs';
6 | const Config = require('electron-store');
7 | import * as log from 'electron-log';
8 | import { parseAL, AL } from 'aigis-fuel';
9 | const config = new Config();
10 | export class Rule {
11 | public Url: Array;
12 | public Method: string;
13 | public Request: boolean;
14 | public Callback: (url: string, response, request?: any) => void;
15 | constructor(
16 | options: { url: string[]; method?: string; request?: any },
17 | callback: (url: string, response, request?: any) => void
18 | ) {
19 | this.Url = options.url;
20 | this.Method = options.method || 'ALL';
21 | this.Request = options.request || false;
22 | this.Callback = callback;
23 | }
24 | match(url: string, method: string) {
25 | function checkUrl(value: string) {
26 | if (url.indexOf(value) !== -1) {
27 | return true;
28 | } else {
29 | return false;
30 | }
31 | }
32 | if (this.Url.find(checkUrl) && (this.Method === 'ALL' || this.Method === method)) {
33 | return true;
34 | }
35 | return null;
36 | }
37 | }
38 | let subscription: Array = [];
39 | let FileList = {};
40 | let mainFontPath = config.get('fontPath');
41 | let browserWindow: BrowserWindow;
42 | export class RequestHandler {
43 | static setFileList(fileList) {
44 | FileList = fileList;
45 | }
46 | static setFontPath(path: string) {
47 | mainFontPath = path;
48 | }
49 | static Clear() {
50 | subscription = [];
51 | }
52 | static setWin(win) {
53 | browserWindow = win;
54 | }
55 | static Subscribe(
56 | options: { url: Array; method?: string; request?: boolean },
57 | callback: (url: string, response, request?: any) => void
58 | ) {
59 | subscription.push(new Rule(options, callback));
60 | }
61 | static async handleData(req, cb) {
62 | // 处理回复用函数
63 | function handleResponse(raw: Buffer, readable: stream.PassThrough, rule: Rule) {
64 | if (!ModifyFilePath) {
65 | // 直接回传
66 | readable.pause();
67 | readable.push(null);
68 | readable.resume();
69 | } else {
70 | // 魔改文件热封装
71 | const result = parseAL(raw);
72 | const packaged = result.Package(ModifyFilePath);
73 | readable.push(packaged);
74 | readable.pause();
75 | readable.push(null);
76 | readable.resume();
77 | }
78 | if (rule) {
79 | const _res = raw;
80 | const _req = req.uploadData ? req.uploadData[0].bytes : null;
81 | rule.Callback(req.url, _res, _req);
82 | }
83 | }
84 |
85 | // 获取发起请求用session
86 | const requestSession = session.fromPartition('persist:request', { cache: true });
87 | // 获取游戏本体session
88 | const gameSession = session.fromPartition('persist:game', { cache: true });
89 | if (browserWindow) {
90 | browserWindow.webContents.send('request-incoming');
91 | }
92 | // 检查该请求是否订阅
93 | const rule = subscription.find(value => value.match(req.url, req.method));
94 | // 创建可读取流
95 | const readable = new stream.PassThrough();
96 | // 判断是否需要进行魔改
97 | let ModifyFilePath = '';
98 | if (req.url.indexOf('://drc1bk94f7rq8.cloudfront.net/') !== -1) {
99 | let modifyFileName = '';
100 | const reqPath = url.parse(req.url).path;
101 | let requestFileName = FileList[reqPath];
102 | if (reqPath.indexOf(mainFontPath) !== -1) {
103 | requestFileName = 'MainFont.aft';
104 | }
105 | if (requestFileName) {
106 | switch (path.extname(requestFileName)) {
107 | case '.atb':
108 | modifyFileName = requestFileName.replace('.atb', '.txt');
109 | break;
110 | case '.aar':
111 | modifyFileName = requestFileName.replace('.aar', '');
112 | break;
113 | default:
114 | modifyFileName = requestFileName;
115 | }
116 | }
117 | const protoablePath = process.env.PORTABLE_EXECUTABLE_DIR;
118 | const userDataPath = app.getPath('userData');
119 | const modPath = protoablePath ? protoablePath + '/mods' : path.join(userDataPath, 'mods');
120 | if (!fs.existsSync(modPath)) {
121 | fs.mkdirSync(modPath);
122 | }
123 | const modifyFilePath = path.join(modPath, modifyFileName);
124 | if (modifyFileName !== '' && fs.existsSync(modifyFilePath)) {
125 | log.info(requestFileName, 'modify by Server');
126 | // AFT和PNG文件直接回传
127 | // 同名文件直接回传
128 | if (fs.existsSync(path.join(modPath, requestFileName))) {
129 | const fileStream = fs.createReadStream(modifyFilePath);
130 | cb({
131 | statusCode: 200,
132 | data: readable
133 | });
134 | fileStream.on('data', chunk => {
135 | readable.push(chunk);
136 | });
137 | fileStream.on('end', () => {
138 | readable.pause();
139 | readable.push(null);
140 | readable.resume();
141 | });
142 | return;
143 | }
144 | // 其他文件
145 | ModifyFilePath = modifyFilePath;
146 | }
147 | }
148 | // 创建请求
149 | const request = net.request({
150 | method: req.method,
151 | url: req.url,
152 | session: requestSession,
153 | // useSessionCookies: true,
154 | referrerPolicy: 'unsafe-url',
155 | // cache: 'force-cache',
156 | // redirect: 'manual'
157 | });
158 |
159 | // 绕过DMM区域检查
160 | // console.log('raw url: ', req.url);
161 | // if ((req.url as string).includes('dmm.com') || (req.url as string).includes('dmm.co.jp')) {
162 | // console.log('modify url:', req.url);
163 | // request.setHeader('X-Forwarded-For', '45.14.106.61');
164 | // }
165 | // 允许分片
166 | // request.chunkedEncoding = true;
167 | // 写Header
168 | if (req.referrer) {
169 | request.setHeader('Referer', req.referrer);
170 | }
171 |
172 | Object.keys(req.headers).forEach(key => {
173 | request.setHeader(key, req.headers[key]);
174 | });
175 | // 写Cookies
176 | const gameCookies = await gameSession.cookies.get({ url: req.url });
177 | const cookieTexts = gameCookies.map(x => `${x.name}=${x.value}`);
178 |
179 | if (cookieTexts.length > 0) {
180 | request.setHeader('Cookie', cookieTexts.join('; '));
181 | }
182 | // request.setHeader('If-None-Match', " ");
183 | // 上传数据
184 | if (req.uploadData) {
185 | for (const v of req.uploadData) {
186 | if (v.type === 'blob') {
187 | const b = await gameSession.getBlobData(v.blobUUID);
188 | request.write(b);
189 | }
190 | if (v.bytes) {
191 | request.write(v.bytes);
192 | }
193 | }
194 | }
195 | // 处理请求
196 | request.on('response', response => {
197 | const raws: Array = [];
198 | let length = 0;
199 | let time = 10;
200 | const timeout = setTimeout(() => {
201 | time--;
202 | if (time === 0) {
203 | console.log(req.url, 'Time out');
204 | }
205 | }, 1000);
206 | // console.log(response.headers);
207 | cb({
208 | statusCode: response.statusCode,
209 | headers: response.headers,
210 | data: readable
211 | });
212 | response.on('data', chunk => {
213 | time = 10;
214 | if (!ModifyFilePath) {
215 | readable.push(chunk);
216 | }
217 | if (rule || ModifyFilePath) {
218 | raws.push(chunk);
219 | }
220 | length += chunk.byteLength;
221 | });
222 | response.on('end', () => {
223 | clearTimeout(timeout);
224 | const raw = raws.length !== 0 ? Buffer.concat(raws) : null;
225 | if (response.statusCode !== 200) {
226 | // 直接回传
227 | readable.pause();
228 | readable.push(null);
229 | readable.resume();
230 | }
231 | handleResponse(raw, readable, rule);
232 | });
233 | response.on('error', err => {
234 | clearTimeout(timeout);
235 | readable.push(null);
236 | log.error(req.url, err, response.statusCode);
237 | });
238 | });
239 | // 处理错误
240 | request.on('error', err => {
241 | log.error(req.url, err);
242 | readable.pause();
243 | readable.push(null);
244 | readable.resume();
245 | });
246 | request.end();
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/app/global/globalSetting.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ElectronService } from '../core/electron.service';
3 | import { AddSetterToObject } from '../core/util';
4 | import * as crypto from 'crypto';
5 | import { GlobalStatusService } from './globalStatus.service';
6 | import { ElMessageService } from 'element-angular';
7 | import { TranslateService } from '@ngx-translate/core';
8 | import { GameModel } from '../core/game.model';
9 | import { Size } from '../core/util';
10 | const Store = require('electron-store');
11 | const config = new Store();
12 |
13 | export const ProxyRule: { [key: string]: string } = {
14 | off: 'direct://',
15 | shimakazego: '127.0.0.1:8099',
16 | acgp: '127.0.0.1:8123',
17 | shadowsocks: 'socks5://127.0.0.1:1080',
18 | custom: ''
19 | };
20 |
21 | export class Proxy {
22 | Type = 'off';
23 | Host = '';
24 | Port = '';
25 | Socks5 = false;
26 | Enabled = false;
27 | }
28 | export class Account {
29 | Name = '新账户';
30 | Username = '';
31 | Password = '';
32 | IsDefault = false;
33 | }
34 | export class AccountList {
35 | Encrypted = false;
36 | EncryptedList = '';
37 | List: Account[] = [];
38 | }
39 |
40 | export class GlobalSetting {
41 | public Proxy = new Proxy();
42 | public SpeedUpKey = '';
43 | public UseSkillKey = '';
44 | public ScreenShotKey = '';
45 | public ReloadKey = '';
46 | public PauseKey = '';
47 | public BulletTimeKey = '';
48 | public Language = 'cn';
49 | public Zoom = 100;
50 | public Opacity = 100;
51 | public Mute = false;
52 | public Lock = false;
53 | public DefaultGame = new GameModel('None', new Size(640, 960), 'about:blank');
54 | public DataCollectPermit = true;
55 | public DisableHardwareAcceleration = false;
56 | }
57 |
58 | const needDispatch = ['Zoom', 'Mute', 'Lock', 'Opacity'];
59 |
60 | @Injectable()
61 | export class GlobalSettingService {
62 | public GlobalSetting = new GlobalSetting();
63 | public AccountList = new AccountList();
64 | constructor(
65 | private electronService: ElectronService,
66 | private globalStatusService: GlobalStatusService,
67 | private translateService: TranslateService,
68 | private message: ElMessageService
69 | ) {
70 | if (window.localStorage.getItem('globalSetting')) {
71 | try {
72 | // 读取全局设置信息
73 | this.GlobalSetting = Object.assign(
74 | this.GlobalSetting,
75 | JSON.parse(window.localStorage.getItem('globalSetting'))
76 | );
77 |
78 | // 装载一些数据给Service
79 | for (let i = 0; i < needDispatch.length; i++) {
80 | const key = needDispatch[i];
81 | this.globalStatusService.GlobalStatusStore.Get(key).Dispatch(this.GlobalSetting[key]);
82 | }
83 | this.globalStatusService.GlobalStatusStore.Get('CurrentGame').Dispatch(this.GlobalSetting.DefaultGame);
84 | } catch { }
85 | }
86 | this.GlobalSetting.DisableHardwareAcceleration = config.get('disable-hardware-acceleration', false) as any;
87 | if (window.localStorage.getItem('accountList')) {
88 | try {
89 | // 读取账户列表
90 | this.AccountList = Object.assign(this.AccountList, JSON.parse(window.localStorage.getItem('accountList')));
91 | if (!this.AccountList.Encrypted) {
92 | this.globalStatusService.GlobalStatusStore.Get('AccountList').Dispatch(
93 | this.AccountList.List.map(v => {
94 | return v.Username;
95 | })
96 | );
97 | const defaultAccount = this.AccountList.List.find(v => v.IsDefault);
98 | if (defaultAccount) {
99 | this.globalStatusService.GlobalStatusStore.Get('SelectedAccount').Dispatch(defaultAccount.Username);
100 | }
101 | }
102 | } catch { }
103 | }
104 |
105 | // 订阅账户设置密码的变化
106 | this.globalStatusService.GlobalStatusStore.Get('AccountListPassword').Subscribe(v => {
107 | if (this.AccountList.Encrypted) {
108 | this.DecryptAccountList();
109 | } else {
110 | this.translateService.get('MESSAGE.SET-SUCCESS').subscribe(res => {
111 | this.message['success'](res);
112 | });
113 | this.SaveAccountList();
114 | }
115 | });
116 |
117 | // 订阅
118 | for (let i = 0; i < needDispatch.length; i++) {
119 | const key = needDispatch[i];
120 | this.globalStatusService.GlobalStatusStore.Get(key).Subscribe(v => {
121 | this.GlobalSetting[key] = v;
122 | });
123 | }
124 |
125 | // 给globalSetting的所有成员加上getter和setter,方便调取saveConfigure
126 | AddSetterToObject(this.GlobalSetting, v => {
127 | this.saveConfigure();
128 | });
129 | }
130 |
131 | public revertDisableHardwareAcceleration() {
132 | this.GlobalSetting.DisableHardwareAcceleration = !this.GlobalSetting.DisableHardwareAcceleration;
133 | config.set('disable-hardware-acceleration', this.GlobalSetting.DisableHardwareAcceleration);
134 | }
135 | private saveConfigure() {
136 | window.localStorage.setItem('globalSetting', JSON.stringify(this.GlobalSetting));
137 | }
138 |
139 | public SaveAccountList() {
140 | // 这里实际上要加密
141 | window.localStorage.setItem('accountList', JSON.stringify(this.AccountList));
142 | const password = this.globalStatusService.GlobalStatusStore.Get('AccountListPassword').Value;
143 | if (password && password !== '') {
144 | const tempAccountList = new AccountList();
145 | tempAccountList.Encrypted = true;
146 | const cipher = crypto.createCipher('aes192', password);
147 | let encrypted = '';
148 | cipher.on('readable', () => {
149 | const data = cipher.read();
150 | if (data) {
151 | encrypted += (data).toString('hex');
152 | }
153 | });
154 | cipher.on('end', () => {
155 | tempAccountList.EncryptedList = encrypted;
156 | window.localStorage.setItem('accountList', JSON.stringify(tempAccountList));
157 | });
158 | cipher.write(JSON.stringify(this.AccountList.List));
159 | cipher.end();
160 | } else {
161 | const tempAccountList = new AccountList();
162 | tempAccountList.Encrypted = false;
163 | tempAccountList.List = this.AccountList.List;
164 | window.localStorage.setItem('accountList', JSON.stringify(tempAccountList));
165 | }
166 | }
167 |
168 | Init() {
169 | // 进行初始化全局设定操作
170 | this.setProxy(this.GlobalSetting.Proxy);
171 | this.globalStatusService.GlobalStatusStore.Get('Zoom').Dispatch(this.GlobalSetting.Zoom);
172 | this.translateService.use(this.GlobalSetting.Language);
173 | }
174 | setProxy(proxy: Proxy) {
175 | if (proxy.Type === undefined) {
176 | proxy.Type = proxy.Enabled ? 'custom' : 'off';
177 | }
178 | this.GlobalSetting.Proxy = proxy;
179 | // 读取规则
180 | let proxyRule = ProxyRule[proxy.Type];
181 | if (proxyRule === '') {
182 | proxyRule = (proxy.Socks5 ? 'socks5://' : '') + `${proxy.Host}:${proxy.Port}`;
183 | }
184 | if (proxyRule === undefined) {
185 | proxyRule = 'direct://';
186 | }
187 | // 不再在Renderer中设置Proxy
188 | // this.electronService.SetProxy(proxyRule);
189 | this.electronService.ipcRenderer.send('proxyStatusUpdate', proxyRule);
190 | }
191 | FindAccount(username) {
192 | return this.AccountList.List.find(v => v.Username === username);
193 | }
194 | DecryptAccountList() {
195 | if (this.AccountList.Encrypted) {
196 | const password = this.globalStatusService.GlobalStatusStore.Get('AccountListPassword').Value;
197 | if (password !== '' && password) {
198 | const decipher = crypto.createDecipher('aes192', password);
199 | let decrypted = '';
200 | decipher.on('readable', () => {
201 | const data = decipher.read();
202 | if (data) {
203 | decrypted += (data).toString('utf-8');
204 | }
205 | });
206 | decipher.on('end', () => {
207 | try {
208 | this.AccountList.List = JSON.parse(decrypted);
209 | this.globalStatusService.GlobalStatusStore.Get('AccountList').Dispatch(
210 | this.AccountList.List.map(v => {
211 | return v.Username;
212 | })
213 | );
214 | this.AccountList.Encrypted = false;
215 | // 密码正确
216 | this.globalStatusService.GlobalStatusStore.Get('AccountListPasswordError').Dispatch(false);
217 | this.translateService.get('MESSAGE.DECRYPT-SUCCESS').subscribe(res => {
218 | this.message['success'](res);
219 | });
220 | } catch { }
221 | });
222 | decipher.on('error', () => {
223 | this.translateService.get('UTIL.PASSWORDERROR').subscribe(res => {
224 | this.message['error'](res);
225 | });
226 | this.globalStatusService.GlobalStatusStore.Get('AccountListPasswordError').Dispatch(true);
227 | });
228 | decipher.write(this.AccountList.EncryptedList, 'hex');
229 | decipher.end();
230 | }
231 | }
232 | }
233 | ForceClearPassword() {
234 | this.AccountList.Encrypted = false;
235 | this.AccountList.EncryptedList = '';
236 | this.SaveAccountList();
237 | }
238 | }
239 |
--------------------------------------------------------------------------------