├── .babelrc
├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── launcher.json
├── package.json
├── src
├── App.less
├── App.less.d.ts
├── App.tsx
├── ads-carousel
│ ├── ads-carousel.less
│ ├── ads-carousel.less.d.ts
│ └── ads-carousel.tsx
├── auth
│ └── auth-client.ts
├── background2.jpg
├── configuration-component
│ ├── configuration-component.less
│ ├── configuration-component.less.d.ts
│ └── configuration-component.tsx
├── gui.tsx
├── icon.ico
├── left-panel
│ ├── left-panel.less
│ ├── left-panel.less.d.ts
│ └── left-panel.tsx
├── login-component
│ ├── login-component.less
│ ├── login-component.less.d.ts
│ └── login-component.tsx
├── main.ts
├── menu-bar
│ ├── menu-bar.less
│ ├── menu-bar.less.d.ts
│ └── menu-bar.tsx
├── news-panel
│ ├── news-panel.less
│ ├── news-panel.less.d.ts
│ └── news-panel.tsx
├── rpc
│ ├── client-library.tsx
│ └── rpc-messages.tsx
├── tslint.json
└── typings.d.ts
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env","@babel/preset-react"]
3 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: 0Lucifer0
4 | patreon: NosCore
5 | ko_fi: noscoreio
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | yarn.lock
27 | package-lock.json
28 | /.vscode
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 NosCoreLegend
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NosCoreLegend #
2 |
3 | ## Screenshots ##
4 |
5 |
6 |
7 |
8 | ## You want to contribute ? ##
9 | [](https://discord.gg/Eu3ETSw)
10 |
11 | ## You like our work ? ##
12 | [](https://ko-fi.com/A3562BQV)
13 | or
14 | Become a Patron!
15 |
16 | ## Achtung! ##
17 | We are not responsible of any damages caused by bad usage of our source. Please before asking questions or installing this source read this readme and also do a research, google is your friend. If you mess up when installing our source because you didnt follow it, we will laugh at you. A lot.
18 |
19 | ## Instructions to contribute ##
20 |
21 |
22 | ### Legal ###
23 | This Website and Project is in no way affiliated with, authorized, maintained, sponsored or endorsed by Gameforge or any of its affiliates or subsidiaries. This is an independent and unofficial launcher for educational use ONLY.
24 | Using the Launcher might be against the TOS.
25 |
26 |
--------------------------------------------------------------------------------
/launcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "Noscore Legend",
3 | "Description": "NosCore Legend is a Nostale private server running on NosCoreIO. This server is meant to be used for testing the NosCore emulator.",
4 | "Links": {
5 | "Website":"",
6 | "Discord":"",
7 | "Support":"",
8 | "Terms Of Use":""
9 | },
10 | "StatusUrl": "",
11 | "Auth": {
12 | "Url":"https://127.0.0.1/api/v1/auth/thin",
13 | "Port":5000
14 | },
15 | "LoginServerIp": "127.0.0.1",
16 | "News": {
17 | "22.06.2019 Double Jackpot Event": "",
18 | "21.06.2019 Event: Triple Fortune": "",
19 | "21.06.2019 NosVille in Football Fever! ": "",
20 | "18.06.2019 Maintenance": "",
21 | "15.06.2019 24hr Happy Hour - let it rain NosDollars!": ""
22 | },
23 | "Ads": {
24 | "First Ads": {"Img":"http://d847yipi3worj.cloudfront.net/NosCoreLegend.png", "Url":"", "Description": ""}
25 | }
26 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "noscore-launcher",
3 | "version": "1.0.0",
4 | "main": "./dist/main.js",
5 | "typings": "typings.d.ts",
6 | "author": "NosCoreLegend",
7 | "description": "Launcher for NosCoreLegend",
8 | "license": "MIT",
9 | "scripts": {
10 | "build": "webpack --mode production --config webpack.config.js && electron-builder build",
11 | "release": "electron-builder build",
12 | "start": "concurrently \"webpack-dev-server --mode development --hot --inline --config webpack.config.js\" \"wait-on http://localhost:8080/ && electron ./dist/main.js\""
13 | },
14 | "build": {
15 | "appId": "com.noscorelegend.launcher",
16 | "asar": true,
17 | "files": [
18 | "dist/**/*",
19 | "package.json",
20 | "src/icon.ico"
21 | ],
22 | "win": {
23 | "target": "portable",
24 | "icon": "src/icon.ico"
25 | },
26 | "portable": {
27 | "artifactName": "NosCoreLegend.exe",
28 | "requestExecutionLevel": "admin",
29 | "useZip": true
30 | }
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.9.0",
34 | "@teamsupercell/typings-for-css-modules-loader": "^2.1.0",
35 | "@types/node": "^13.9.3",
36 | "@types/react": "^16.9.25",
37 | "@types/react-dom": "^16.9.5",
38 | "babel-loader": "^8.1.0",
39 | "babel-preset-es2015-node": "^6.1.1",
40 | "babel-preset-react": "^6.24.1",
41 | "clean-webpack-plugin": "^3.0.0",
42 | "css-loader": "^3.4.2",
43 | "electron": "^8.1.1",
44 | "electron-builder": "^22.4.1",
45 | "html-webpack-hot-plugin": "^1.2.2",
46 | "html-webpack-plugin": "^3.2.0",
47 | "image-webpack-loader": "^6.0.0",
48 | "standard": "^14.3.3",
49 | "standard-loader": "^7.0.0",
50 | "style-loader": "^1.1.3",
51 | "ts-loader": "^6.2.2",
52 | "tslint": "^6.1.0",
53 | "tslint-config-standard": "^9.0.0",
54 | "tslint-loader": "^3.5.4",
55 | "typescript": "^3.8.3",
56 | "url-loader": "^4.0.0",
57 | "webpack": "^4.42.0",
58 | "webpack-cli": "^3.3.11",
59 | "webpack-dev-server": "^3.10.3"
60 | },
61 | "dependencies": {
62 | "@babel/preset-env": "^7.9.0",
63 | "@babel/preset-react": "^7.9.1",
64 | "@types/request": "^2.48.4",
65 | "@types/webpack-env": "^1.15.1",
66 | "bootstrap": "^4.4.1",
67 | "concurrently": "^5.1.0",
68 | "electron-is-dev": "^1.1.0",
69 | "electron-store": "^5.1.1",
70 | "guid-typescript": "^1.0.9",
71 | "less": "^3.11.1",
72 | "less-loader": "^5.0.0",
73 | "mini-css-extract-plugin": "^0.9.0",
74 | "react": "^16.13.1",
75 | "react-bootstrap": "^1.0.0-beta.17",
76 | "react-dom": "^16.13.1",
77 | "react-icons": "^3.9.0",
78 | "request": "^2.88.2",
79 | "wait-on": "^4.0.1",
80 | "winreg": "^1.2.4"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/App.less:
--------------------------------------------------------------------------------
1 | ::-webkit-scrollbar { display: none; }
2 |
3 | .App {
4 | text-align: center;
5 | background-image: url("background2.jpg");
6 | background-position: left top;
7 | background-repeat: no-repeat;
8 | background-size: cover;
9 | width:960px;
10 | height:680px;
11 | overflow: hidden;
12 | color:lightgray;
13 | text-shadow:
14 | -1px -1px 0 #000,
15 | 1px -1px 0 #000,
16 | -1px 1px 0 #000,
17 | 1px 1px 0 #000;
18 | user-select: none;
19 | text-align: left;
20 | }
21 |
--------------------------------------------------------------------------------
/src/App.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace AppLessModule {
2 | export interface IAppLess {
3 | App: string;
4 | }
5 | }
6 |
7 | declare const AppLessModule: AppLessModule.IAppLess & {
8 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
9 | locals: AppLessModule.IAppLess;
10 | };
11 |
12 | export = AppLessModule;
13 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './App.less';
3 | import 'bootstrap/dist/css/bootstrap.css';
4 | import { MenuBar } from './menu-bar/menu-bar';
5 | import { LeftPanel } from './left-panel/left-panel';
6 | import { NewsPanel } from './news-panel/news-panel';
7 | import { AuthInformation } from './auth/auth-client';
8 |
9 | class App extends React.Component<{}, AuthInformation> {
10 | server: any;
11 | constructor(props: {}) {
12 | super(props);
13 | this.state = { token: '', platformGameAccountId: '', user: '' };
14 | }
15 |
16 | getAuthInfo = (state: AuthInformation) => {
17 | this.setState(state);
18 | }
19 |
20 | render() {
21 | return (
22 |
33 | );
34 | }
35 | }
36 |
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/src/ads-carousel/ads-carousel.less:
--------------------------------------------------------------------------------
1 | .adsCarousel {
2 | width:350px;
3 | overflow:hidden;
4 | border-radius: 10px;
5 | a {
6 | color: lightblue;
7 | }
8 | }
9 | :global(.carousel-caption) {
10 | background: rgba(0, 0, 0, .4);
11 | width:100%;
12 | right:0;
13 | bottom: 0;
14 | left:0;
15 | padding-bottom: 20px;
16 | padding-top: 0px;
17 | font-size: 18px;
18 | }
19 | :global(.carousel-indicators) {
20 | bottom: -20px;
21 | }
22 |
23 | .carouselItem {
24 | height: 200px;
25 | }
26 |
27 | .carouselItem img {
28 | max-height:150%;
29 | max-width:100%;
30 | object-fit: cover;
31 | }
--------------------------------------------------------------------------------
/src/ads-carousel/ads-carousel.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace AdsCarouselLessModule {
2 | export interface IAdsCarouselLess {
3 | adsCarousel: string;
4 | carouselItem: string;
5 | }
6 | }
7 |
8 | declare const AdsCarouselLessModule: AdsCarouselLessModule.IAdsCarouselLess & {
9 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
10 | locals: AdsCarouselLessModule.IAdsCarouselLess;
11 | };
12 |
13 | export = AdsCarouselLessModule;
14 |
--------------------------------------------------------------------------------
/src/ads-carousel/ads-carousel.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Carousel from 'react-bootstrap/Carousel';
3 | import styles from './ads-carousel.less';
4 | import Store from 'electron-store';
5 |
6 | export class AdsCarousel extends React.Component {
7 | constructor(props: any, context: any) {
8 | super(props, context);
9 |
10 | this.handleSelect = this.handleSelect.bind(this);
11 |
12 | this.state = {
13 | index: 0,
14 | direction: null
15 | };
16 | }
17 |
18 | handleSelect(selectedIndex: any, e: { direction: any; }) {
19 | this.setState({
20 | index: selectedIndex,
21 | direction: e.direction
22 | });
23 | }
24 |
25 | renderAds = () => {
26 | const store = new Store();
27 | const ads = store.get('configuration').Ads;
28 | return Object.keys(ads).map((index: string) =>
29 |
30 |
31 |
36 |
37 | );
38 | }
39 |
40 | render() {
41 | return (
42 |
43 | {this.renderAds()}
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/auth/auth-client.ts:
--------------------------------------------------------------------------------
1 | import request from "request";
2 | import { Guid } from "guid-typescript";
3 |
4 | export interface AuthInformation {
5 | platformGameAccountId: string;
6 | token: string;
7 | user: string;
8 | }
9 |
10 | export class AuthClient {
11 | user: string;
12 | password: string;
13 | language: string;
14 | url: string;
15 | port: number;
16 | path: string;
17 | code: string;
18 | installationId: string;
19 |
20 | constructor(
21 | user: string,
22 | password: string,
23 | language: string,
24 | url: string,
25 | path: string,
26 | port: number
27 | ) {
28 | this.user = user;
29 | this.password = password;
30 | this.language = language;
31 | this.url = url;
32 | this.port = port;
33 | this.path = path;
34 | this.code = "";
35 | this.installationId = "";
36 | }
37 |
38 | getSessionToken = async () => {
39 | return new Promise((resolve, reject) => {
40 | process.env["_TNT_SESSION_ID"] = Guid.create().toString();
41 | // process.env["_TNT_CLIENT_APPLICATION_ID"] = "d3b2a0c1-f0d0-4888-ae0b-1c5e1febdafb";
42 |
43 | const data = {
44 | gfLang: this.language.substring(0, 2),
45 | identity: this.user,
46 | locale: this.language,
47 | password: this.password,
48 | platformGameId: "dd4e22d6-00d1-44b9-8126-d8b40e0cd7c9"
49 | };
50 |
51 | const options = {
52 | url: this.url + ":" + this.port + this.path,
53 | headers: {
54 | "Content-Type": "application/json",
55 | "Content-Length": Buffer.byteLength(JSON.stringify(data))
56 | },
57 | json: data
58 | };
59 |
60 | request.post(options, (err: any, res: any, body: any) => {
61 | if (body) {
62 | if (body.token && body.platformGameAccountId) {
63 | resolve({
64 | user: this.user,
65 | token: body.token,
66 | platformGameAccountId: body.platformGameAccountId
67 | });
68 | } else {
69 | resolve({ error: body.toString() });
70 | }
71 | } else if (err) {
72 | resolve({
73 | error:
74 | "Something went wrong while trying communicate with the server. The server may be down."
75 | });
76 | }
77 | });
78 | });
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/background2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NosCoreLegend/Launcher/9dfc63a0326e409027c68a9bf96b6df01b093433/src/background2.jpg
--------------------------------------------------------------------------------
/src/configuration-component/configuration-component.less:
--------------------------------------------------------------------------------
1 | .configurationMenu {
2 | background-image: url("../background2.jpg");
3 | background-position: left top;
4 | background-size: cover;
5 | width: 960px;
6 | height: 680px;
7 | overflow: hidden;
8 | }
9 |
10 | .configureButton {
11 | width: 100%;
12 | }
13 |
14 | .label {
15 | font-size: smaller;
16 | }
--------------------------------------------------------------------------------
/src/configuration-component/configuration-component.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace ConfigurationComponentLessModule {
2 | export interface IConfigurationComponentLess {
3 | configurationMenu: string;
4 | configureButton: string;
5 | label: string;
6 | }
7 | }
8 |
9 | declare const ConfigurationComponentLessModule: ConfigurationComponentLessModule.IConfigurationComponentLess & {
10 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
11 | locals: ConfigurationComponentLessModule.IConfigurationComponentLess;
12 | };
13 |
14 | export = ConfigurationComponentLessModule;
15 |
--------------------------------------------------------------------------------
/src/configuration-component/configuration-component.tsx:
--------------------------------------------------------------------------------
1 | import { FaUserCircle, FaCog } from "react-icons/fa";
2 | import React, { Fragment } from "react";
3 | import Button from "react-bootstrap/Button";
4 | import Modal from "react-bootstrap/Modal";
5 | import Form from "react-bootstrap/Form";
6 | import styles from "./configuration-component.less";
7 | import { AuthInformation, AuthClient } from "../auth/auth-client";
8 | import { JSonRpcResult, JSonRpcMessage } from "../rpc/rpc-messages";
9 | import { ClientLibrary } from "../rpc/client-library";
10 | import Store from "electron-store";
11 |
12 | interface ConfigurationComponentState {
13 | showMenu: boolean;
14 | error: string;
15 | client: string | undefined;
16 | apidll: string | undefined;
17 | }
18 |
19 | export interface ConfigurationComponentProps {}
20 |
21 | export class ConfigurationComponent extends React.Component<
22 | {},
23 | ConfigurationComponentState
24 | > {
25 | server: any;
26 |
27 | constructor(
28 | props: ConfigurationComponentState,
29 | state: ConfigurationComponentState
30 | ) {
31 | super(props, state);
32 |
33 | this.state = {
34 | showMenu: false,
35 | error: "",
36 | apidll: undefined,
37 | client: undefined
38 | };
39 |
40 | this.showMenu = this.showMenu.bind(this);
41 | this.closeMenu = this.closeMenu.bind(this);
42 | }
43 |
44 | componentDidMount() {
45 | const store = new Store();
46 | const configuration = store.get("user-configuration");
47 |
48 | this.setState({
49 | client: configuration?.client ?? undefined,
50 | apidll: configuration?.apidll ?? undefined,
51 | error: "",
52 | showMenu: this.state.showMenu
53 | });
54 | }
55 |
56 | configurationForm = (event: any) => {
57 | event.preventDefault();
58 | const client = (document.getElementById("clientpath") as HTMLInputElement)
59 | .files;
60 | const apidll = (document.getElementById("dllpath") as HTMLInputElement)
61 | .files;
62 | const store = new Store();
63 | if (client && apidll) {
64 | store.set("user-configuration", {
65 | client: client[0]?.path,
66 | apidll: apidll[0]?.path
67 | });
68 | this.setState({
69 | showMenu: !this.state.showMenu,
70 | error: "",
71 | client: client[0]?.path,
72 | apidll: apidll[0]?.path
73 | });
74 | }
75 | };
76 |
77 | showMenu = (event: React.MouseEvent) => {
78 | event.preventDefault();
79 | this.setState({
80 | showMenu: !this.state.showMenu,
81 | error: "",
82 | client: this.state.client,
83 | apidll: this.state.apidll
84 | });
85 | };
86 |
87 | closeMenu = () => {
88 | this.setState({
89 | showMenu: false,
90 | error: "",
91 | client: this.state.client,
92 | apidll: this.state.apidll
93 | });
94 | };
95 |
96 | render() {
97 | return (
98 |
99 |
100 |
106 |
107 | Configuration
108 |
109 |
110 |
115 | NostaleClientX.exe path
116 | {this.state.client}
117 |
122 |
123 |
124 | gameforge_client_api.dll path
125 | {this.state.apidll}
126 |
131 |
132 |
133 |
140 |
141 |
142 |
143 |
144 | );
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/gui.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementsByTagName('body')[0]);
6 |
--------------------------------------------------------------------------------
/src/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NosCoreLegend/Launcher/9dfc63a0326e409027c68a9bf96b6df01b093433/src/icon.ico
--------------------------------------------------------------------------------
/src/left-panel/left-panel.less:
--------------------------------------------------------------------------------
1 | .playButton {
2 | position: absolute;
3 | width: 250px;
4 | bottom: 80px;
5 | border-radius: 0;
6 | }
7 |
8 | .progressBar {
9 | position: absolute;
10 | width: 80%;
11 | bottom: 80px;
12 | background: rgba(0, 0, 0, .6);
13 | filter: contrast(100%);
14 | height: 48px;
15 | }
16 |
17 | .leftPanel {
18 | p {
19 | font-weight: bold;
20 | font-size: 14px;
21 | }
22 |
23 | text-align: left;
24 | height: 600px;
25 | margin-top: -20px;
26 | }
27 |
28 | .leftNavBar {
29 | font-size: 12px;
30 | margin-top: 50px;
31 | background: rgba(0, 0, 0, .6);
32 | filter: contrast(100%);
33 | text-align: center;
34 | border-radius: 4px;
35 | margin-left: 50px;
36 | width: 300px;
37 | color: darkgrey;
38 |
39 | a {
40 | color: white;
41 | }
42 |
43 | ul {
44 | padding-inline-start: 0;
45 | list-style: none;
46 | display: inline-block;
47 | text-align: left;
48 | margin-bottom: 0;
49 | padding: 0px 0px 2px 0px;
50 |
51 | li {
52 | display: inline-block;
53 | }
54 |
55 | li+li:before {
56 | content: "|";
57 | font-size: large;
58 | margin-left: 5px;
59 | margin-right: 5px;
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/left-panel/left-panel.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace LeftPanelLessModule {
2 | export interface ILeftPanelLess {
3 | leftNavBar: string;
4 | leftPanel: string;
5 | playButton: string;
6 | progressBar: string;
7 | }
8 | }
9 |
10 | declare const LeftPanelLessModule: LeftPanelLessModule.ILeftPanelLess & {
11 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
12 | locals: LeftPanelLessModule.ILeftPanelLess;
13 | };
14 |
15 | export = LeftPanelLessModule;
16 |
--------------------------------------------------------------------------------
/src/left-panel/left-panel.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import Button from "react-bootstrap/Button";
3 | import ProgressBar from "react-bootstrap/ProgressBar";
4 | import styles from "./left-panel.less";
5 | import { AuthInformation } from "../auth/auth-client";
6 | import Store from "electron-store";
7 |
8 | const nosDirectory = "C:\\Program Files (x86)\\NosTale\\";
9 |
10 | export class LeftPanel extends React.Component {
11 | startNostale = () => {
12 | if (this.props.user === "") {
13 | return;
14 | }
15 | console.log("start patching");
16 | const store = new Store();
17 | const configuration = store.get("user-configuration");
18 | const executablePath = `${configuration?.client.substring(
19 | 0,
20 | configuration?.client.lastIndexOf("\\") + 1
21 | ) ?? nosDirectory}NosCore.exe`;
22 | const fs = require("fs");
23 |
24 | const parameters = ["gf", "2"]; //i'm lazy but this is the client language
25 | const ip = "127.0.0.1";
26 | const port = 4000;
27 |
28 | fs.readFile(
29 | configuration?.client ?? `${nosDirectory}NostaleClientX.exe`,
30 | "binary",
31 | async function(err: any, data: any) {
32 | if (err) {
33 | return console.log(err);
34 | }
35 | let result = data;
36 |
37 | // change port
38 | const portRegexp = new RegExp(
39 | `${String.fromCharCode(0)}[${String.fromCharCode(
40 | 160
41 | )}-${String.fromCharCode(169)}]${String.fromCharCode(
42 | 15
43 | )}${String.fromCharCode(0)}`,
44 | "g"
45 | );
46 | result = result.replace(
47 | portRegexp,
48 | String.fromCharCode(0) +
49 | String.fromCharCode(Number("0x" + port.toString(16).substr(1, 2))) +
50 | String.fromCharCode(
51 | Number("0x0" + port.toString(16).substr(0, 1))
52 | ) +
53 | String.fromCharCode(0)
54 | );
55 |
56 | // change ip
57 | const endOfIp =
58 | String.fromCharCode(0) + String.fromCharCode(255).repeat(4);
59 | const reg = /\d{2,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g;
60 | let ipToReplace;
61 |
62 | // tslint:disable-next-line: no-conditional-assignment
63 | while ((ipToReplace = reg.exec(result)) !== null) {
64 | const lengthDiff = 15 - ipToReplace.toString().length;
65 | const re = new RegExp(
66 | ipToReplace.toString() +
67 | String.fromCharCode(0).repeat(lengthDiff) +
68 | endOfIp +
69 | ".",
70 | "g"
71 | );
72 | result = result
73 | .toString()
74 | .replace(
75 | re,
76 | ip +
77 | String.fromCharCode(0).repeat(15 - ip.length) +
78 | String.fromCharCode(0) +
79 | String.fromCharCode(255).repeat(4) +
80 | String.fromCharCode(ip.length)
81 | );
82 | }
83 | await fs.writeFile(executablePath, result, "binary", async function(err: any) {
84 | if (err) {
85 | return console.log(err);
86 | }
87 | console.log(`${executablePath} generated!`);
88 | await fs.copyFile(
89 | configuration?.apidll,
90 | (configuration?.client.substring(
91 | 0,
92 | configuration?.client.lastIndexOf("\\") + 1
93 | ) ?? nosDirectory) + "gameforge_client_api.dll",
94 | function(err: any) {
95 | if (err) {
96 | return console.log(err);
97 | }
98 | console.log("gameforge_client_api.dll copied!");
99 | require("child_process").execFile(executablePath, parameters);
100 | }
101 | );
102 | });
103 | }
104 | );
105 | };
106 |
107 | render() {
108 | const store = new Store();
109 | return (
110 |
111 |
{store.get("configuration").Title}
112 |
{store.get("configuration").Description}
113 |
124 |
132 | {/*
*/}
133 |
134 | );
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/login-component/login-component.less:
--------------------------------------------------------------------------------
1 | .loginLogo {
2 | -webkit-app-region: no-drag;
3 | font-size: 35px;
4 | color: grey;
5 | margin-right: 5px;
6 | text-align: center;
7 | display: inline-block;
8 | z-index: 10000;
9 |
10 | &:hover {
11 | color: darkgrey;
12 | }
13 |
14 | &.online {
15 | color: #007bff
16 | }
17 | }
18 |
19 | .userId {
20 | display: inline;
21 | align-content: center;
22 | }
23 |
24 | .login {
25 | display: inline-block;
26 | width: 160px;
27 | margin-top: 10px;
28 | }
29 |
30 | .loginMenu {
31 | background-image: url("../background2.jpg");
32 | background-position: left top;
33 | background-size: cover;
34 | width: 960px;
35 | height: 680px;
36 | overflow: hidden;
37 | }
38 |
39 | .loginButton {
40 | width: 100%;
41 | }
42 |
43 | .logoutMenu {
44 | padding-left: 50px;
45 | padding-top: 50px;
46 | color: grey;
47 | margin-left: -40px;
48 | margin-top: -50px;
49 | height: auto;
50 | width: 170px;
51 | background: rgba(0, 0, 0, .7);
52 | }
--------------------------------------------------------------------------------
/src/login-component/login-component.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace LoginComponentLessModule {
2 | export interface ILoginComponentLess {
3 | login: string;
4 | loginButton: string;
5 | loginLogo: string;
6 | loginMenu: string;
7 | logoutMenu: string;
8 | online: string;
9 | userId: string;
10 | }
11 | }
12 |
13 | declare const LoginComponentLessModule: LoginComponentLessModule.ILoginComponentLess & {
14 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
15 | locals: LoginComponentLessModule.ILoginComponentLess;
16 | };
17 |
18 | export = LoginComponentLessModule;
19 |
--------------------------------------------------------------------------------
/src/login-component/login-component.tsx:
--------------------------------------------------------------------------------
1 | import { FaUserCircle } from "react-icons/fa";
2 | import React, { Fragment } from "react";
3 | import Button from "react-bootstrap/Button";
4 | import Modal from "react-bootstrap/Modal";
5 | import Form from "react-bootstrap/Form";
6 | import styles from "./login-component.less";
7 | import { AuthInformation, AuthClient } from "../auth/auth-client";
8 | import { JSonRpcResult, JSonRpcMessage } from "../rpc/rpc-messages";
9 | import { ClientLibrary } from "../rpc/client-library";
10 | import Store from "electron-store";
11 |
12 | interface LoginComponentState {
13 | showMenu: boolean;
14 | userName: string;
15 | error: string;
16 | }
17 |
18 | export interface LoginComponentProps {
19 | authCallBack: (state: AuthInformation) => void;
20 | }
21 |
22 | export class LoginComponent extends React.Component<
23 | LoginComponentProps,
24 | LoginComponentState
25 | > {
26 | server: any;
27 | constructor(props: LoginComponentProps, state: LoginComponentState) {
28 | super(props, state);
29 |
30 | this.state = {
31 | showMenu: false,
32 | userName: "",
33 | error: ""
34 | };
35 |
36 | this.showMenu = this.showMenu.bind(this);
37 | this.closeMenu = this.closeMenu.bind(this);
38 | }
39 |
40 | componentDidMount() {
41 | const store = new Store();
42 | const creds = store.get("credentials");
43 | if (creds) {
44 | this.login(creds.password, creds.account);
45 | }
46 | }
47 |
48 | login = async (password: string, email: string) => {
49 | const store = new Store();
50 | if (email && password) {
51 | let authclient = new AuthClient(
52 | email,
53 | password,
54 | "fr-FR",
55 | "http://127.0.0.1",
56 | "/api/v1/auth/thin/sessions",
57 | 5000
58 | );
59 | let authInfo = await authclient.getSessionToken();
60 | if (authInfo && (authInfo as AuthInformation).token) {
61 | this.props.authCallBack(authInfo as AuthInformation);
62 | store.set("credentials", { account: email, password: password });
63 | if (this.server) {
64 | this.server.close();
65 | }
66 |
67 | this.startPipe(authInfo as AuthInformation);
68 | this.setState({ showMenu: false, userName: email, error: "" });
69 | } else {
70 | this.setState({
71 | showMenu: true,
72 | userName: "",
73 | error: (authInfo as any).error
74 | });
75 | }
76 | }
77 | };
78 |
79 | startPipe = (state: AuthInformation) => {
80 | console.log("starting pipe");
81 | const clientLibrary = new ClientLibrary(
82 | "http://127.0.0.1",
83 | "/api/v1/auth/thin/codes",
84 | 5000,
85 | state
86 | );
87 |
88 | let net = require("net");
89 | let PIPE_NAME = "GameforgeClientJSONRPC";
90 | let PIPE_PATH = "\\\\.\\pipe\\" + PIPE_NAME;
91 |
92 | if (state.platformGameAccountId === "" || state.token === "") {
93 | console.log("invalid account");
94 | return;
95 | }
96 |
97 | this.server = net.createServer((stream: any) => {
98 | stream.on("data", async (data: any) => {
99 | console.log(
100 | "New packet received",
101 | String.fromCharCode.apply(null, data)
102 | );
103 | const obj = JSON.parse(
104 | String.fromCharCode.apply(null, data)
105 | ) as JSonRpcMessage;
106 | let returnMessage = {
107 | id: obj.id,
108 | jsonrpc: obj.jsonrpc,
109 | result: ""
110 | } as JSonRpcResult;
111 | switch (obj.method) {
112 | case "ClientLibrary.isClientRunning":
113 | returnMessage.result = clientLibrary.IsClientRunning(
114 | obj.params.sessionId
115 | );
116 | break;
117 | case "ClientLibrary.initSession":
118 | returnMessage.result = clientLibrary.InitSession(
119 | obj.params.sessionId
120 | );
121 | break;
122 | case "ClientLibrary.queryAuthorizationCode":
123 | returnMessage.result = await clientLibrary.QueryAuthorizationCode(
124 | obj.params.sessionId
125 | );
126 | break;
127 | case "ClientLibrary.queryGameAccountName":
128 | returnMessage.result = clientLibrary.QueryGameAccountName(
129 | obj.params.sessionId
130 | );
131 | break;
132 | }
133 | stream.write(JSON.stringify(returnMessage));
134 | console.log("New packet sent", JSON.stringify(returnMessage));
135 | });
136 | });
137 | this.server.listen(PIPE_PATH);
138 | console.log("pipe started");
139 | };
140 |
141 | showMenu = (event: React.MouseEvent) => {
142 | event.preventDefault();
143 | this.setState({
144 | showMenu: !this.state.showMenu,
145 | userName: this.state.userName,
146 | error: ""
147 | });
148 | };
149 |
150 | closeMenu = () => {
151 | this.setState({
152 | showMenu: false,
153 | userName: this.state.userName,
154 | error: ""
155 | });
156 | };
157 |
158 | logout = () => {
159 | this.setState({ showMenu: false, userName: "", error: "" });
160 | this.props.authCallBack({ platformGameAccountId: "", token: "", user: "" });
161 | this.server.close();
162 | };
163 |
164 | loginForm = (event: any) => {
165 | event.preventDefault();
166 | let password = (document.getElementById("password") as HTMLInputElement)
167 | .value;
168 | let email = (document.getElementById("email") as HTMLInputElement).value;
169 | this.login(password, email);
170 | };
171 |
172 | render() {
173 | const { userName } = this.state;
174 | return (
175 |
176 |
177 |
183 | {userName !== "" ? (
184 | {userName || ""}
185 | ) : (
186 | ""
187 | )}
188 |
189 | {userName === "" ? (
190 |
196 |
197 | Login
198 |
199 |
200 |
205 | Login/Email address
206 |
212 |
213 |
214 | Password
215 |
221 |
222 |
229 |
230 |
231 |
232 | ) : (
233 | this.state.showMenu && (
234 |
235 |
236 |
239 |
240 |
241 | )
242 | )}
243 |
244 | );
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow } from 'electron';
2 | import request from 'request';
3 | import isDev from 'electron-is-dev';
4 | import Store from 'electron-store';
5 | declare var __dirname: string;
6 | let mainWindow: Electron.BrowserWindow;
7 |
8 | function onReady() {
9 | mainWindow = new BrowserWindow({
10 | frame: false, width: 960, height: 680, resizable: false,
11 | webPreferences: {
12 | nodeIntegration: true
13 | }
14 | });
15 | const configUrl = 'https://d847yipi3worj.cloudfront.net/launcher.json';
16 | const store = new Store();
17 | request.get(configUrl).on('response', (response: any) => {
18 | let body = '';
19 | response.on('data', function (d: any) {
20 | body += d;
21 | });
22 | response.on('end', function () {
23 | store.set('configuration', JSON.parse(body));
24 | });
25 | });
26 |
27 | const fileName = isDev ? 'http://localhost:8080' : `file://${__dirname}/index.html`;
28 | mainWindow.loadURL(fileName).then().catch();
29 | mainWindow.on('close', () => app.quit());
30 | if (isDev) {
31 | mainWindow.webContents.openDevTools({ mode: 'detach' });
32 | }
33 | }
34 |
35 | app.on('ready', () => onReady());
36 | app.on('window-all-closed', () => app.quit());
37 |
--------------------------------------------------------------------------------
/src/menu-bar/menu-bar.less:
--------------------------------------------------------------------------------
1 | .menu {
2 | -webkit-app-region: drag;
3 | width: 100%;
4 | height: 60px;
5 | margin-bottom: 40px;
6 | background: rgba(0, 0, 0, .4);
7 | filter: contrast(100%);
8 | z-index: 9999;
9 | position: relative;
10 |
11 | ul {
12 | list-style-type: none;
13 |
14 | li {
15 | display: inline-block;
16 | }
17 | }
18 | }
19 |
20 | .optionLogo {
21 | -webkit-app-region: no-drag;
22 | font-size: 35px;
23 | color: grey;
24 | margin-right: 5px;
25 | text-align: center;
26 | float: right;
27 | margin-right: 10px;
28 |
29 | &:hover {
30 | color: darkgrey
31 | }
32 | }
33 |
34 | .closeLogo {
35 | -webkit-app-region: no-drag;
36 | font-size: 35px;
37 | color: grey;
38 | margin-right: 5px;
39 | text-align: center;
40 | float: right;
41 | margin-right: 10px;
42 |
43 | &:hover {
44 | color: darkgrey
45 | }
46 | }
47 |
48 | .smallLogo {
49 | padding: 0 20px 0 0;
50 | margin-top: 10px;
51 | margin-bottom: 10px;
52 | width: 80px;
53 | height: 60px;
54 | }
--------------------------------------------------------------------------------
/src/menu-bar/menu-bar.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace MenuBarLessModule {
2 | export interface IMenuBarLess {
3 | closeLogo: string;
4 | menu: string;
5 | optionLogo: string;
6 | smallLogo: string;
7 | }
8 | }
9 |
10 | declare const MenuBarLessModule: MenuBarLessModule.IMenuBarLess & {
11 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
12 | locals: MenuBarLessModule.IMenuBarLess;
13 | };
14 |
15 | export = MenuBarLessModule;
16 |
--------------------------------------------------------------------------------
/src/menu-bar/menu-bar.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { FaCog, FaTimes } from 'react-icons/fa';
3 | import styles from './menu-bar.less';
4 | import { LoginComponent, LoginComponentProps } from '../login-component/login-component';
5 | import { ConfigurationComponent } from '../configuration-component/configuration-component';
6 |
7 | export class MenuBar extends React.Component {
8 | handleClose = (e: any) => {
9 | e.preventDefault();
10 | window.close();
11 | }
12 |
13 | render() {
14 | return (
15 |
16 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/news-panel/news-panel.less:
--------------------------------------------------------------------------------
1 | .newsPanel {
2 | font-size: 12px;
3 | background: rgba(0, 0, 0, .6);
4 | filter: contrast(100%);
5 | padding: 5px 5px 5px 5px;
6 | width: 350px;
7 | margin-bottom: 40px;
8 |
9 | ul {
10 | list-style-type: none;
11 | margin-left: -20px;
12 | }
13 |
14 | a {
15 | color: lightblue;
16 | }
17 | }
18 |
19 | .statusPanel {
20 | font-size: 12px;
21 | background: rgba(0, 0, 0, .6);
22 | filter: contrast(100%);
23 | padding: 5px 5px 5px 5px;
24 | width: 350px;
25 | margin-bottom: 40px;
26 | }
27 |
28 | .statusPanel ul {
29 | list-style-type: none;
30 | margin-left: -20px;
31 | }
--------------------------------------------------------------------------------
/src/news-panel/news-panel.less.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace NewsPanelLessModule {
2 | export interface INewsPanelLess {
3 | newsPanel: string;
4 | statusPanel: string;
5 | }
6 | }
7 |
8 | declare const NewsPanelLessModule: NewsPanelLessModule.INewsPanelLess & {
9 | /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
10 | locals: NewsPanelLessModule.INewsPanelLess;
11 | };
12 |
13 | export = NewsPanelLessModule;
14 |
--------------------------------------------------------------------------------
/src/news-panel/news-panel.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { GiCalendar } from 'react-icons/gi';
3 | import { FaStar, FaUsers, FaLightbulb } from 'react-icons/fa';
4 | import status from './news-panel.less';
5 | import { AdsCarousel } from '../ads-carousel/ads-carousel';
6 | import Store from 'electron-store';
7 |
8 | export class NewsPanel extends React.Component {
9 | render() {
10 | const store = new Store();
11 | return (
12 |
13 |
14 |
Status
15 |
16 | - Server Online
17 | - 299 players
18 |
19 |
20 |
21 |
News & Events
22 |
{Object.keys(store.get('configuration').News).map((index: string) => - {index}
)}
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/rpc/client-library.tsx:
--------------------------------------------------------------------------------
1 | import request from 'request';
2 | import { AuthInformation } from '../auth/auth-client';
3 |
4 | export class ClientLibrary {
5 | url: string;
6 | authInfo: AuthInformation;
7 | port: number;
8 | path: string;
9 | code: string;
10 | installationId: string;
11 |
12 | constructor(url: string, path: string, port: number, authInfo: AuthInformation) {
13 | this.url = url;
14 | this.authInfo = authInfo;
15 | this.port = port;
16 | this.path = path;
17 | this.code = '';
18 | this.installationId = '';
19 | }
20 |
21 | getAuthorizationCode = async () => {
22 | return new Promise((resolve, reject) => {
23 | let Registry = require('winreg');
24 | let regKey = new Registry({
25 | hive: Registry.HKCU,
26 | key: '\\Software\\Gameforge4d\\TNTClient\\MainApp'
27 | });
28 | regKey.values((err: any, items: any) => {
29 | if (err) {
30 | console.log('ERROR: ' + err);
31 | } else {
32 | for (let i = 0; i < items.length; i++) {
33 | if (items[i].name === 'InstallationId') {
34 | this.installationId = items[i].value;
35 | }
36 | }
37 | }
38 | });
39 |
40 | const data = {
41 | PlatformGameAccountId: this.authInfo.platformGameAccountId
42 | };
43 |
44 | const options = {
45 | url: this.url + ':' + this.port + this.path,
46 | headers: {
47 | 'TNT-Installation-Id': this.installationId,
48 | 'User-Agent': 'TNTClientMS2/1.3.39',
49 | 'Authorization': `Bearer ${this.authInfo.token}`,
50 | 'Content-Type': 'application/json',
51 | 'Content-Length': Buffer.byteLength(JSON.stringify(data))
52 | },
53 | json: data
54 | };
55 |
56 | request.post(options, (err: any, res: any, body: any) => {
57 | if (body) {
58 | this.code = body.code;
59 | resolve(body);
60 | } else if (err) {
61 | reject(err);
62 | }
63 | });
64 | });
65 | }
66 |
67 | QueryGameAccountName = (sessionId: string) => {
68 | return this.authInfo.user;
69 | }
70 |
71 | QueryAuthorizationCode = async (sessionId: string) => {
72 | await this.getAuthorizationCode();
73 | return this.code;
74 | }
75 |
76 | InitSession = (sessionId: string) => {
77 | return sessionId;
78 | }
79 |
80 | IsClientRunning = (sessionId: string) => {
81 | return true;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/rpc/rpc-messages.tsx:
--------------------------------------------------------------------------------
1 | export interface JSonRpcMessage {
2 | id: number;
3 | jsonrpc: string;
4 | method: string;
5 | params: any;
6 | }
7 |
8 | export interface JSonRpcResult {
9 | id: number;
10 | jsonrpc: string;
11 | result: any;
12 | }
13 |
--------------------------------------------------------------------------------
/src/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "semicolon": [true, "always"],
4 | "space-before-function-paren": false
5 | }
6 | }
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NosCoreLegend/Launcher/9dfc63a0326e409027c68a9bf96b6df01b093433/src/typings.d.ts
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": false,
19 | "jsx": "preserve",
20 |
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
4 | const HtmlWebpackHotPlugin = require('html-webpack-hot-plugin')
5 |
6 | let htmlHotPlugin = new HtmlWebpackHotPlugin({ hot: true });
7 | let mode = process.argv[process.argv.indexOf('--mode') + 1];
8 | console.log(`webpack mode is ${process.argv[process.argv.indexOf('--mode') + 1]}`)
9 | if (mode === 'development') {
10 | htmlHotPlugin = new HtmlWebpackHotPlugin({ hot: true });
11 | }
12 | const commonConfig = {
13 | mode: mode,
14 | devtool: mode === 'production' ? "" : "source-map",
15 | node: {
16 | __dirname: false
17 | },
18 | output: {
19 | path: path.resolve(__dirname, 'dist'),
20 | filename: '[name].js'
21 | },
22 | devServer: {
23 | writeToDisk: true,
24 | before(app, server) {
25 | if (mode === 'development') {
26 | htmlHotPlugin.setDevServer(server)
27 | }
28 | }
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts$/,
34 | enforce: 'pre',
35 | loader: 'tslint-loader',
36 | options: {
37 | typeCheck: true,
38 | emitErrors: true
39 | }
40 | },
41 | {
42 | test: /\.tsx?$/,
43 | loader: ['babel-loader', 'ts-loader']
44 | },
45 | {
46 | test: /\.js$/,
47 | enforce: 'pre',
48 | loader: 'standard-loader',
49 | options: {
50 | typeCheck: true,
51 | emitErrors: true
52 | }
53 | },
54 | {
55 | test: /\.jsx?$/,
56 | loader: ['babel-loader']
57 | },
58 | {
59 | test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/,
60 | loader: 'url-loader',
61 | },
62 | {
63 | test: /\.css$/,
64 | use: ['style-loader', 'css-loader']
65 | },
66 | {
67 | test: /\.less$/,
68 | use: [
69 | 'style-loader',
70 | '@teamsupercell/typings-for-css-modules-loader',
71 | { loader: 'css-loader', options: { modules:true, sourceMap: true } },
72 | "less-loader"
73 | ]
74 | },
75 | ]
76 | },
77 | resolve: {
78 | extensions: ['.js', '.ts', '.tsx', '.jsx', '.json', '.gif', '.png', '.jpg', '.jpeg', '.svg', '.less', '.css']
79 | }
80 | }
81 |
82 | module.exports = [
83 | Object.assign(
84 | {
85 | target: 'electron-main',
86 | entry: { main: './src/main.ts' },
87 | plugins: [
88 | mode === 'production' ? new CleanWebpackPlugin() : false,
89 | ].filter(Boolean)
90 | },
91 | commonConfig),
92 |
93 | Object.assign(
94 | {
95 | target: 'electron-renderer',
96 | entry: { gui: './src/gui.tsx' },
97 | plugins: [
98 | new HtmlWebpackPlugin({
99 | hash: true,
100 | filename: 'index.html',
101 | title: 'NosCoreLegend',
102 | }),
103 | mode === 'development' ? htmlHotPlugin : false].filter(Boolean)
104 | },
105 | commonConfig)
106 | ];
--------------------------------------------------------------------------------