├── .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 | [![Discord](https://i.gyazo.com/2115a3ecb258220f5b1a8ebd8c50eb8f.png)](https://discord.gg/Eu3ETSw) 10 | 11 | ## You like our work ? ## 12 | [![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](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 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
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 | {ads[index].Description} 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 |
111 | {this.state.error !== "" && ( 112 |
{this.state.error}
113 | )} 114 | 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 |
201 | {this.state.error !== "" && ( 202 |
{this.state.error}
203 | )} 204 | 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 | ]; --------------------------------------------------------------------------------