Merge pull request #763 from Pax1601/747-improve-configurator-for-automatic-saved-games-folder-detection

747 improve configurator for automatic saved games folder detection
This commit is contained in:
Pax1601 2023-12-20 16:58:32 +01:00 committed by GitHub
commit ce28d49510
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 162533 additions and 2807 deletions

6
.gitignore vendored
View File

@ -22,4 +22,8 @@ client/public/javascripts/leaflet.nauticscale.js
client/public/javascripts/L.Path.Drag.js
/installer/archive/Scripts
/installer/archive/Mods
/installer/installer/DCSOlympus*.exe
/installer/installer/DCSOlympus*.exe
L.Path.Drag.js
leaflet-gesture-handling.css
package-lock.json

View File

@ -1,49 +1,59 @@
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var fs = require('fs');
var bodyParser = require('body-parser');
module.exports = function (configLocation) {
/* Requires */
var express = require('express');
var path = require('path');
var logger = require('morgan');
var fs = require('fs');
var bodyParser = require('body-parser');
const { createProxyMiddleware } = require('http-proxy-middleware');
var atcRouter = require('./routes/api/atc');
var airbasesRouter = require('./routes/api/airbases');
var elevationRouter = require('./routes/api/elevation');
var databasesRouter = require('./routes/api/databases');
var indexRouter = require('./routes/index');
var uikitRouter = require('./routes/uikit');
var usersRouter = require('./routes/users');
var resourcesRouter = require('./routes/resources');
var pluginsRouter = require('./routes/plugins');
/* Default routers */
var atcRouter = require('./routes/api/atc');
var airbasesRouter = require('./routes/api/airbases');
var elevationRouter = require('./routes/api/elevation');
var databasesRouter = require('./routes/api/databases')(path.join(path.dirname(configLocation), "..", "Mods", "Services", "Olympus", "databases"));
var indexRouter = require('./routes/index');
var uikitRouter = require('./routes/uikit');
var usersRouter = require('./routes/users');
var resourcesRouter = require('./routes/resources');
var pluginsRouter = require('./routes/plugins');
var app = express();
/* Load the config and create the express app */
let config = {}
console.log(`Loading configuration file from ${configLocation}`)
if (fs.existsSync(configLocation)){
let rawdata = fs.readFileSync(configLocation);
config = JSON.parse(rawdata);
}
else {
console.error("Error loading configuration file.")
return undefined;
}
var app = express();
app.use(logger('dev'));
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
/* Define middleware */
app.use(logger('dev'));
app.use('/olympus', createProxyMiddleware({ target: `http://${config["server"]["address"]}:${config["server"]["port"]}`, changeOrigin: true }));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/api/atc', atcRouter);
app.use('/api/airbases', airbasesRouter);
app.use('/api/elevation', elevationRouter);
app.use('/api/databases', databasesRouter);
app.use('/plugins', pluginsRouter)
app.use('/users', usersRouter);
app.use('/uikit', uikitRouter);
app.use('/resources', resourcesRouter);
/* Apply routers */
app.use('/', indexRouter);
app.use('/api/atc', atcRouter);
app.use('/api/airbases', airbasesRouter);
app.use('/api/elevation', elevationRouter);
app.use('/api/databases', databasesRouter);
app.use('/plugins', pluginsRouter)
app.use('/users', usersRouter);
app.use('/uikit', uikitRouter);
app.use('/resources', resourcesRouter);
app.set('view engine', 'ejs');
app.set('view engine', 'ejs');
return app;
}
let rawdata = fs.readFileSync('../olympus.json');
let config = JSON.parse(rawdata);
if (config["server"] != undefined)
app.get('/config', (req, res) => res.send(config["server"]));
module.exports = app;
const DemoDataGenerator = require('./demo.js');
var demoDataGenerator = new DemoDataGenerator(app, config);

116
client/bin/demo Normal file
View File

@ -0,0 +1,116 @@
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
console.log('\x1b[36m%s\x1b[0m', "* | __ \\ / ____|/ ____| / __ \\| | *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | | (___ | | | | |_ _ _ __ ___ _ __ _ _ ___ *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | \\___ \\ | | | | | | | | '_ ` _ \\| '_ \\| | | / __| *");
console.log('\x1b[36m%s\x1b[0m', "* | |__| | |____ ____) | | |__| | | |_| | | | | | | |_) | |_| \\__ \\ *");
console.log('\x1b[36m%s\x1b[0m', "* |_____/ \\_____|_____/ \\____/|_|\\__, |_| |_| |_| .__/ \\__,_|___/ *");
console.log('\x1b[36m%s\x1b[0m', "* __/ | | | *");
console.log('\x1b[36m%s\x1b[0m', "* |___/ |_| *");
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "");
console.log("Please wait while DCS Olympus DEMO Backend Server starts up...");
var fs = require('fs');
let rawdata = fs.readFileSync('../olympus.json');
let config = JSON.parse(rawdata);
/**
* Module dependencies.
*/
var app = require('../demo');
var debug = require('debug')('client:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var configPort = null;
if (config["server"] != undefined && config["server"]["port"] != undefined) {
configPort = config["server"]["port"];
}
var port = normalizePort(configPort || '3000');
app.set('port', port);
console.log("Express server listening on port: " + port)
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
console.log("DCS Olympus DEMO Backend Server {{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}} started correctly!")
console.log("Waiting for connections...")
process.title = `DCS Olympus DEMO Backend Server {{OLYMPUS_VERSION_NUMBER}} (${port})`;

View File

@ -1,4 +1,9 @@
#!/usr/bin/env node
const yargs = require('yargs');
var fs = require('fs');
/* Define configuration parameter */
yargs.alias('c', 'config').describe('c', 'olympus.json config location').string('rp');
args = yargs.argv;
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
@ -12,107 +17,89 @@ console.log('\x1b[36m%s\x1b[0m', "* |___/
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "");
console.log("Please wait while DCS Olympus Server starts up...");
console.log(`Config location: ${args["config"]}`)
var fs = require('fs');
let rawdata = fs.readFileSync('../olympus.json');
let config = JSON.parse(rawdata);
/* Load the configuration file */
var clientPort = 0;
if (fs.existsSync(args["config"])) {
var json = JSON.parse(fs.readFileSync(args["config"], 'utf-8'));
clientPort = json["client"]["port"];
} else {
console.log("Failed to read config, aborting!");
/**
* Module dependencies.
*/
/* Wait a bit before closing the window */
await new Promise(resolve => setTimeout(resolve, 3000));
return;
}
var app = require('../app');
/* Load the dependencies. The app is loaded providing the configuration file location */
var app = require('../app')(args["config"]);
var debug = require('debug')('client:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var configPort = null;
if (config["client"] != undefined && config["client"]["port"] != undefined) {
configPort = config["client"]["port"];
}
var port = normalizePort(configPort || '3000');
/* Normalize port */
var port = normalizePort(clientPort);
app.set('port', port);
console.log("Express server listening on port: " + port)
/**
* Create HTTP server.
*/
/* Create HTTP server */
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
/* Listen on provided port, on all network interfaces. */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
/* Normalize a port into a number, string, or false. */
function normalizePort(val) {
var port = parseInt(val, 10);
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (isNaN(port)) {
return val;
}
if (port >= 0) {
// port number
return port;
}
if (port >= 0) {
return port;
}
return false;
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
/* Event listener for HTTP server "error" event. */
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
/* Handle specific listen errors with friendly messages */
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
/* Event listener for HTTP server "listening" event. */
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
/* Final user friendly printing */
console.log("DCS Olympus server {{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}} started correctly!")
console.log("Waiting for connections...")
process.title = `DCS Olympus server {{OLYMPUS_VERSION_NUMBER}} (${port})`;

57
client/client.js Normal file
View File

@ -0,0 +1,57 @@
const { app, BrowserWindow } = require('electron/main')
const path = require('path')
const fs = require('fs')
const { spawn } = require('child_process');
const yargs = require('yargs');
yargs.alias('c', 'config').describe('c', 'olympus.json config location').string('rp');
args = yargs.argv;
console.log(`Config location: ${args["config"]}`)
var clientPort = 3000;
if (fs.existsSync(args["config"])) {
var json = JSON.parse(fs.readFileSync(args["config"], 'utf-8'));
clientPort = json["client"]["port"];
} else {
console.log("Failed to read config, trying default port");
}
function createWindow() {
const win = new BrowserWindow({
icon: "./../img/olympus.ico"
})
win.loadURL(`http://localhost:${clientPort}`);
win.setMenuBarVisibility(false);
win.maximize();
}
app.whenReady().then(() => {
const server = spawn('node', [path.join('.', 'bin', 'www'), "--config", args["config"]]);
server.stdout.on('data', (data) => {
console.log(`${data}`);
});
server.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Child process exited with code ${code}`);
});
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})

1
client/client.vbs Normal file
View File

@ -0,0 +1 @@
CreateObject("Wscript.Shell").Run "npm run client -- --config """&WScript.Arguments(0)&"""", 0

View File

@ -3,7 +3,11 @@ const path = require('path')
const yargs = require('yargs');
const prompt = require('prompt-sync')({sigint: true});
const sha256 = require('sha256');
const jsonPath = path.join('..', 'olympus.json');
var jsonPath = path.join('..', 'olympus.json');
var regedit = require('regedit')
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
/* Set the acceptable values */
yargs.alias('a', 'address').describe('a', 'Backend address').string('a');
@ -12,6 +16,7 @@ yargs.alias('c', 'clientPort').describe('c', 'Client port').number('c');
yargs.alias('p', 'gameMasterPassword').describe('p', 'Game Master password').string('p');
yargs.alias('bp', 'blueCommanderPassword').describe('bp', 'Blue Commander password').string('bp');
yargs.alias('rp', 'redCommanderPassword').describe('rp', 'Red Commander password').string('rp');
yargs.alias('d', 'directory').describe('d', 'Directory where the DCS Olympus configurator is located').string('rp');
args = yargs.argv;
async function run() {
@ -30,21 +35,6 @@ async function run() {
if (args.address === undefined && args.clientPort === undefined && args.backendPort === undefined &&
args.gameMasterPassword === undefined && args.blueCommanderPassword === undefined && args.redCommanderPassword === undefined) {
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
console.log('\x1b[36m%s\x1b[0m', "* | __ \\ / ____|/ ____| / __ \\| | *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | | (___ | | | | |_ _ _ __ ___ _ __ _ _ ___ *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | \\___ \\ | | | | | | | | '_ ` _ \\| '_ \\| | | / __| *");
console.log('\x1b[36m%s\x1b[0m', "* | |__| | |____ ____) | | |__| | | |_| | | | | | | |_) | |_| \\__ \\ *");
console.log('\x1b[36m%s\x1b[0m', "* |_____/ \\_____|_____/ \\____/|_|\\__, |_| |_| |_| .__/ \\__,_|___/ *");
console.log('\x1b[36m%s\x1b[0m', "* __/ | | | *");
console.log('\x1b[36m%s\x1b[0m', "* |___/ |_| *");
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "");
console.log("DCS Olympus configurator {{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}}");
console.log("");
var newValue;
var result;
@ -113,5 +103,65 @@ async function run() {
await new Promise(resolve => setTimeout(resolve, 3000));
}
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
console.log('\x1b[36m%s\x1b[0m', "* | __ \\ / ____|/ ____| / __ \\| | *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | | (___ | | | | |_ _ _ __ ___ _ __ _ _ ___ *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | \\___ \\ | | | | | | | | '_ ` _ \\| '_ \\| | | / __| *");
console.log('\x1b[36m%s\x1b[0m', "* | |__| | |____ ____) | | |__| | | |_| | | | | | | |_) | |_| \\__ \\ *");
console.log('\x1b[36m%s\x1b[0m', "* |_____/ \\_____|_____/ \\____/|_|\\__, |_| |_| |_| .__/ \\__,_|___/ *");
console.log('\x1b[36m%s\x1b[0m', "* __/ | | | *");
console.log('\x1b[36m%s\x1b[0m', "* |___/ |_| *");
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "");
console.log("DCS Olympus configurator {{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}}");
console.log("");
/* Run the configurator */
run();
if (args.directory) {
jsonPath = path.join(args.directory, "olympus.json");
}
else {
/* Automatically detect possible DCS installation folders */
regedit.list(shellFoldersKey, function(err, result) {
if (err) {
console.log(err);
}
else {
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined)
{
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
const folders = fs.readdirSync(searchpath);
var options = [];
folders.forEach((folder) => {
if (fs.existsSync(path.join(searchpath, folder, "Logs", "dcs.log"))) {
options.push(folder);
}
})
console.log("The following DCS Saved Games folders have been automatically detected.")
options.forEach((folder, index) => {
console.log(`(${index + 1}) ${folder}`)
});
while (true) {
var newValue = prompt(`Please choose a folder onto which the configurator shall operate by typing the associated number: `)
result = Number(newValue);
if (!isNaN(result) && Number.isInteger(result) && result > 0 && result <= options.length) {
jsonPath = path.join(searchpath, options[result - 1], "Config", "olympus.json");
break;
}
else {
console.log(`Please type a number between 1 and ${options.length}`);
}
}
} else {
console.error("An error occured while trying to fetch the location of the DCS folder. Please type the folder location manually.")
jsonPath = path.join(prompt(`DCS Saved Games folder location: `), "olympus.json");
}
console.log(`Configurator will run on ${jsonPath}, if this is incorrect please restart the configurator`)
run();
}
})
}

View File

@ -1,4 +1,4 @@
copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css
copy .\\node_modules\\leaflet-gesture-handling\\dist\\leaflet-gesture-handling.css .\\public\\stylesheets\\leaflet\\leaflet-gesture-handling.css
copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js
copy .\\node_modules\\leaflet-path-drag\\dist\\L.Path.Drag.js .\\public\\javascripts\\L.Path.Drag.js
xcopy /S /Q /Y /F .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css
xcopy /S /Q /Y /F .\\node_modules\\leaflet-gesture-handling\\dist\\leaflet-gesture-handling.css .\\public\\stylesheets\\leaflet\\leaflet-gesture-handling.css
xcopy /S /Q /Y /F .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js
xcopy /S /Q /Y /F .\\node_modules\\leaflet-path-drag\\dist\\L.Path.Drag.js .\\public\\javascripts\\L.Path.Drag.js

View File

@ -1,2 +0,0 @@
start cmd /k "npm run start"
start cmd /k "watchify .\src\index.ts --debug -o .\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]

1
client/demo.bat Normal file
View File

@ -0,0 +1 @@
node .\bin\demo

View File

@ -1,7 +1,17 @@
const { random } = require('@turf/turf');
var basicAuth = require('express-basic-auth')
var logger = require('morgan');
var enc = new TextEncoder();
var express = require('express');
var fs = require('fs');
let rawdata = fs.readFileSync('../olympus.json');
let config = JSON.parse(rawdata);
var app = express();
app.use(logger('dev'));
const aircraftDatabase = require('./public/databases/units/aircraftdatabase.json');
const helicopterDatabase = require('./public/databases/units/helicopterdatabase.json');
const groundUnitDatabase = require('./public/databases/units/groundunitdatabase.json');
@ -16,16 +26,16 @@ const DEMO_WEAPONS_DATA = {
class DemoDataGenerator {
constructor(app, config)
{
app.get('/demo/units', (req, res) => this.units(req, res));
app.get('/demo/weapons', (req, res) => this.weapons(req, res));
app.get('/demo/logs', (req, res) => this.logs(req, res));
app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res));
app.get('/demo/airbases', (req, res) => this.airbases(req, res));
app.get('/demo/mission', (req, res) => this.mission(req, res));
app.get('/demo/commands', (req, res) => this.command(req, res));
app.put('/demo', (req, res) => this.put(req, res));
app.get('/olympus/units', (req, res) => this.units(req, res));
app.get('/olympus/weapons', (req, res) => this.weapons(req, res));
app.get('/olympus/logs', (req, res) => this.logs(req, res));
app.get('/olympus/bullseyes', (req, res) => this.bullseyes(req, res));
app.get('/olympus/airbases', (req, res) => this.airbases(req, res));
app.get('/olympus/mission', (req, res) => this.mission(req, res));
app.get('/olympus/commands', (req, res) => this.command(req, res));
app.put('/olympus', (req, res) => this.put(req, res));
app.use('/demo', basicAuth({
app.use('/olympus', basicAuth({
users: {
'admin': config["authentication"]["gameMasterPassword"],
'blue': config["authentication"]["blueCommanderPassword"],
@ -511,4 +521,6 @@ class DemoDataGenerator {
}
module.exports = DemoDataGenerator;
var demoDataGenerator = new DemoDataGenerator(app, config);
module.exports = app;

View File

@ -1,2 +1 @@
call npm install --omit=dev
call npm install yargs prompt-sync sha256 tcp-ping-port
npm install --omit=dev

4014
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "DCSOlympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
"main": "client.js",
"version": "{{OLYMPUS_VERSION_NUMBER}}",
"private": true,
"scripts": {
@ -10,27 +10,37 @@
"emit-declarations": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outfile ./@types/olympus/index.d.ts",
"copy": "copy.bat",
"start": "node ./bin/www",
"debug": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon --ignore ./public/databases/ ./bin/www\"",
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
"debug": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon --ignore ./public/databases/ ./bin/www -- --config ../moc_dcs/Config/olympus.json\"",
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]",
"client": "electron ."
},
"dependencies": {
"@turf/turf": "^6.5.0",
"appjs": "^0.0.20",
"appjs-win32": "^0.0.19",
"body-parser": "^1.20.2",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "^3.1.8",
"express": "~4.16.1",
"electron": "^28.0.0",
"express": "^4.18.2",
"express-basic-auth": "^1.2.1",
"http-proxy-middleware": "^2.0.6",
"js-sha256": "^0.10.1",
"leaflet-gesture-handling": "^1.2.2",
"morgan": "~1.9.1",
"node-hide-console-window": "^2.2.0",
"open": "^10.0.0",
"prompt-sync": "^4.2.0",
"regedit": "^5.1.2",
"save": "^2.9.0",
"sha256": "^0.2.0",
"srtm-elevation": "^2.1.2",
"uuid": "^9.0.1"
"tcp-ping-port": "^1.0.1",
"uuid": "^9.0.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@tanem/svg-injector": "^10.1.68",
"@turf/turf": "^6.5.0",
"@types/formatcoords": "^1.1.0",
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0",
@ -46,22 +56,16 @@
"geodesy": "^1.1.2",
"leaflet": "^1.9.3",
"leaflet-control-mini-map": "^0.4.0",
"leaflet-gesture-handling": "^1.2.2",
"leaflet-path-drag": "*",
"leaflet.nauticscale": "^1.1.0",
"nodemon": "^2.0.20",
"nodemon": "^3.0.2",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tinyify": "^4.0.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"
},
"window": {
"width": 1000,
"height": 800,
"position": "center",
"icon": "public/images/icon.png"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,38 +0,0 @@
L.Control.ScaleNautic = L.Control.Scale.extend({
options: {
nautic: false
},
_addScales: function(options, className, container) {
L.Control.Scale.prototype._addScales.call(this, options, className, container);
L.setOptions(options);
if (this.options.nautic) {
this._nScale = L.DomUtil.create('div', className, container);
}
},
_updateScales: function (maxMeters) {
L.Control.Scale.prototype._updateScales.call(this, maxMeters);
if (this.options.nautic && maxMeters) {
this._updateNautic(maxMeters);
}
},
_updateNautic: function (maxMeters) {
var scale = this._nScale,
maxNauticalMiles = maxMeters / 1852, nauticalMiles;
if(maxMeters >= 1852) {
nauticalMiles = L.Control.Scale.prototype._getRoundNum.call(this, maxNauticalMiles);
} else {
nauticalMiles = maxNauticalMiles > 0.1 ? Math.round(maxNauticalMiles * 10) / 10 : Math.round(maxNauticalMiles * 100) / 100;
}
scale.style.width = Math.round(this.options.maxWidth * (nauticalMiles / maxNauticalMiles)) - 10 + 'px';
scale.innerHTML = nauticalMiles + ' nm';
}
});
L.control.scalenautic = function (options) {
return new L.Control.ScaleNautic(options);
};

View File

@ -1,47 +0,0 @@
@-webkit-keyframes leaflet-gestures-fadein {
0% {
opacity: 0; }
100% {
opacity: 1; } }
@keyframes leaflet-gestures-fadein {
0% {
opacity: 0; }
100% {
opacity: 1; } }
.leaflet-container:after {
-webkit-animation: leaflet-gestures-fadein 0.8s backwards;
animation: leaflet-gestures-fadein 0.8s backwards;
color: #fff;
font-family: "Roboto", Arial, sans-serif;
font-size: 22px;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 15px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 461;
pointer-events: none; }
.leaflet-gesture-handling-touch-warning:after,
.leaflet-gesture-handling-scroll-warning:after {
-webkit-animation: leaflet-gestures-fadein 0.8s forwards;
animation: leaflet-gestures-fadein 0.8s forwards; }
.leaflet-gesture-handling-touch-warning:after {
content: attr(data-gesture-handling-touch-content); }
.leaflet-gesture-handling-scroll-warning:after {
content: attr(data-gesture-handling-scroll-content); }

View File

@ -1,58 +1,60 @@
const express = require('express');
const router = express.Router();
const fs = require("fs");
const path = require("path");
module.exports = function (databasesLocation) {
const express = require('express');
const router = express.Router();
const fs = require("fs");
const path = require("path");
router.get('/:type/:name', function (req, res) {
console.log(req.params.database)
});
router.get('/:type/:name', function (req, res) {
console.log(req.params.database)
});
router.put('/save/:type/:name', function (req, res) {
var dir = path.join("./public/databases", req.params.type, "old");
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
router.put('/save/:type/:name', function (req, res) {
var dir = path.join(databasesLocation, req.params.type, "old");
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
var filepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join("./public/databases/", req.params.type, "old", req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
if (fs.existsSync(newFilepath)) {
try {
var json = JSON.stringify(req.body.blueprints, null, "\t" );
fs.writeFileSync(filepath, json, 'utf8');
res.send("OK");
} catch {
var filepath = path.join(databasesLocation, req.params.type, req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join(databasesLocation, req.params.type, "old", req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
if (fs.existsSync(newFilepath)) {
try {
var json = JSON.stringify(req.body.blueprints, null, "\t");
fs.writeFileSync(filepath, json, 'utf8');
res.send("OK");
} catch {
res.status(422).send("Error");
}
} else {
res.status(422).send("Error");
}
} else {
res.status(422).send("Error");
res.status(404).send('Not found');
}
} else {
res.status(404).send('Not found');
}
});
});
router.put('/reset/:type/:name', function (req, res) {
var filepath = path.join("./public/databases", req.params.type, "default", req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
res.send("OK");
} else {
res.status(404).send('Not found');
}
});
router.put('/reset/:type/:name', function (req, res) {
var filepath = path.join(databasesLocation, req.params.type, "default", req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join(databasesLocation, req.params.type, req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
res.send("OK");
} else {
res.status(404).send('Not found');
}
});
router.put('/restore/:type/:name', function (req, res) {
var filepath = path.join("./public/databases", req.params.type, "old", req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
res.send("OK");
} else {
res.status(404).send('Not found');
}
});
router.put('/restore/:type/:name', function (req, res) {
var filepath = path.join(databasesLocation, req.params.type, "old", req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join(databasesLocation, req.params.type, req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
res.send("OK");
} else {
res.status(404).send('Not found');
}
});
module.exports = router;
return router;
}

View File

@ -1,23 +1,8 @@
const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
var themesMap = {};
router.get('/theme/*', function (req, res, next) {
/* If this is the first time this session makes a request, create a uuid and save it to the map. Default theme is the olympus theme */
if (!req.cookies.id) {
const id = uuidv4();
res.cookie('id', id, { httpOnly: true });
themesMap[id] = "olympus";
reqTheme = "olympus";
}
else {
/* If it is present, recover the session theme from the map */
if (!(req.cookies.id in themesMap))
themesMap[req.cookies.id] = "olympus";
reqTheme = themesMap[req.cookies.id];
}
var reqTheme = "olympus";
/* Yes, this in an easter egg! :D Feel free to ignore it, or activate the parrot theme to check what it does. Why parrots? The story is a bit long, come to the Discord and ask :D */
if (reqTheme === "parrot" && !req.url.includes(".css"))
@ -27,15 +12,6 @@ router.get('/theme/*', function (req, res, next) {
});
router.put('/theme/:newTheme', function (req, res, next) {
/* Add the theme to the map, if this session already has an id */
const newTheme = req.params.newTheme;
if (req.cookies.id) {
themesMap[req.cookies.id] = newTheme;
console.log("Theme set to " + newTheme + " for session " + req.cookies.id);
} else {
console.log("Failed to set theme to " + newTheme + ", no session id");
}
res.end("Ok");
});

View File

@ -1,46 +0,0 @@
const fs = require('fs')
const path = require('path')
const { exec } = require("child_process");
const { tcpPingPort } = require("tcp-ping-port")
const jsonPath = path.join('..', 'olympus.json');
const options = {
socketTimeout: 1000
}
var attempt = 0;
const maxAttempt = 3;
function checkServer() {
console.log("Checking DCS Olympus server availability...");
tcpPingPort(`localhost`, json["client"]["port"], options).then(res => {
if (res.online) {
run();
}
else {
if (attempt < maxAttempt) {
attempt++;
console.log(`DCS Olympus server not found, starting it up! Attempt ${attempt} of ${maxAttempt}`);
startServer();
} else {
console.log("Failed to start DCS Olympus server!")
}
}
})
}
async function startServer() {
exec(`START /min "" "../DCS Olympus Server.lnk"`)
await new Promise(resolve => setTimeout(resolve, 3000));
checkServer();
}
function run() {
exec(`start http://localhost:${json["client"]["port"]}`)
}
/* Check that we can read the json */
if (fs.existsSync(jsonPath)) {
var json = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
checkServer();
}

1
client/server.vbs Normal file
View File

@ -0,0 +1 @@
CreateObject("Wscript.Shell").Run "npm run start -- --config """&WScript.Arguments(0)&"""", 1

View File

@ -11,11 +11,6 @@ declare global {
function getOlympusPlugin(): OlympusPlugin;
}
export interface ConfigurationOptions {
port: number;
address: string;
}
export interface ContextMenuOption {
tooltip: string;
src: string;

View File

@ -24,13 +24,11 @@ import { aircraftDatabase } from "./unit/databases/aircraftdatabase";
import { helicopterDatabase } from "./unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "./unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
import { ConfigurationOptions } from "./interfaces";
import { UnitListPanel } from "./panels/unitlistpanel";
import { ContextManager } from "./context/contextmanager";
import { Context } from "./context/context";
var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
var DEBUG = false;
export class OlympusApp {
/* Global data */
@ -216,19 +214,8 @@ export class OlympusApp {
this.#pluginsManager = new PluginsManager();
/* Load the config file from the app server*/
this.getServerManager().getConfig((config: ConfigurationOptions) => {
if (config && config.address != undefined && config.port != undefined) {
const address = config.address;
const port = config.port;
if (typeof address === 'string' && typeof port == 'number') {
this.getServerManager().setAddress(address == "*" ? window.location.hostname : address, port);
}
}
else {
throw new Error('Could not read configuration file');
}
});
/* Set the address of the server */
this.getServerManager().setAddress(window.location.href.split('?')[0]);
/* Setup all global events */
this.#setupEvents();
@ -294,16 +281,7 @@ export class OlympusApp {
});
const shortcutManager = this.getShortcutManager();
shortcutManager.addKeyboardShortcut("toggleDemo", {
"altKey": false,
"callback": () => {
if (DEBUG === true) this.getServerManager().toggleDemoEnabled();
},
"code": "KeyT",
"context": "olympus",
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("togglePause", {
shortcutManager.addKeyboardShortcut("togglePause", {
"altKey": false,
"callback": () => {
this.getServerManager().setPaused(!this.getServerManager().getPaused());

View File

@ -11,13 +11,11 @@ import { zeroAppend } from '../other/utils';
export class ServerManager {
#connected: boolean = false;
#paused: boolean = false;
#REST_ADDRESS = "http://localhost:30000/olympus";
#DEMO_ADDRESS = window.location.href.split('?')[0] + "demo"; /* Remove query parameters */
#REST_ADDRESS = "http://localhost:3001/olympus";
#username = "";
#password = "";
#sessionHash: string | null = null;
#lastUpdateTimes: { [key: string]: number } = {}
#demoEnabled = false;
#previousMissionElapsedTime: number = 0; // Track if mission elapsed time is increasing (i.e. is the server paused)
#serverIsPaused: boolean = false;
#intervals: number[] = [];
@ -32,10 +30,6 @@ export class ServerManager {
this.#lastUpdateTimes[MISSION_URI] = Date.now();
}
toggleDemoEnabled() {
this.#demoEnabled = !this.#demoEnabled;
}
setCredentials(newUsername: string, newPassword: string) {
this.#username = newUsername;
this.#password = newPassword;
@ -63,7 +57,7 @@ export class ServerManager {
optionsString = `commandHash=${options.commandHash}`;
/* On the connection */
xmlHttp.open("GET", `${this.#demoEnabled ? this.#DEMO_ADDRESS : this.#REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
xmlHttp.open("GET", `${this.#REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
/* If provided, set the credentials */
if (this.#username && this.#password)
@ -106,7 +100,7 @@ export class ServerManager {
PUT(request: object, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", this.#demoEnabled ? this.#DEMO_ADDRESS : this.#REST_ADDRESS);
xmlHttp.open("PUT", this.#REST_ADDRESS);
xmlHttp.setRequestHeader("Content-Type", "application/json");
if (this.#username && this.#password)
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${this.#username}:${this.#password}`));
@ -130,8 +124,8 @@ export class ServerManager {
xmlHttp.send(null);
}
setAddress(address: string, port: number) {
this.#REST_ADDRESS = `http://${address}:${port}/olympus`
setAddress(address: string) {
this.#REST_ADDRESS = `${address}olympus`
console.log(`Setting REST address to ${this.#REST_ADDRESS}`)
}

View File

@ -43,21 +43,27 @@ fs.readFile("./version.json", "utf8", (error, data) => {
}
if (data.search(/{{OLYMPUS_VERSION_NUMBER}}/g) >= 0) {
console.log(`Replacing version in ${file}`);
data = data.replace(/{{OLYMPUS_VERSION_NUMBER}}/g, `v${major}.${minor}.${minorminor}`);
data = data.replace(/{{OLYMPUS_COMMIT_HASH}}/g, revision);
fileChanged = true;
}
if (data.search(/{{OLYMPUS_VS_VERSION_NUMBER_1}}/g) >= 0) {
if (data.search(/FILEVERSION \d,\d,\d/g) >= 0) {
console.log(`Replacing version in ${file}`);
var data = data.replace(/{{OLYMPUS_VS_VERSION_NUMBER_1}}/g, `${major},${minor},${minorminor}`);
var data = data.replace(/FILEVERSION \d,\d,\d/g, `FILEVERSION ${major},${minor},${minorminor}`);
fileChanged = true;
}
if (data.search(/{{OLYMPUS_VS_VERSION_NUMBER_2}}/g) >= 0) {
if (data.search(/VALUE "FileVersion", "\d.\d.\d.0"/g) >= 0) {
console.log(`Replacing version in ${file}`);
data = data.replace(/{{OLYMPUS_VS_VERSION_NUMBER_2}}/g, `${major}.${minor}.${minorminor}`);
data = data.replace(/VALUE "FileVersion", "\d.\d.\d.0"/g, `VALUE "FileVersion", "${major}.${minor}.${minorminor}.0"`);
fileChanged = true;
}
if (data.search(/VALUE "ProductVersion", "\d.\d.\d.0"/g) >= 0) {
console.log(`Replacing version in ${file}`);
data = data.replace(/VALUE "ProductVersion", "\d.\d.\d.0"/g, `VALUE "ProductVersion", "${major}.${minor}.${minorminor}.0"`);
fileChanged = true;
}

View File

@ -3,15 +3,16 @@
[Setup]
AppName=DCS Olympus
AppVerName=DCS Olympus {#version}
DefaultDirName={usersavedgames}\DCS.openbeta
DefaultDirName={usersavedgames}\DCS Olympus
DefaultGroupName=DCSOlympus
OutputBaseFilename=DCSOlympus_{#version}
UninstallFilesDir={app}\Mods\Services\Olympus
UninstallFilesDir={app}
SetupIconFile="..\img\olympus.ico"
DirExistsWarning=no
AppendDefaultDirName=no
LicenseFile="..\LEGAL.txt"
ChangesEnvironment=yes
PrivilegesRequired=lowest
InfoBeforeFile="..\notes.txt"
[Messages]
WizardSelectDir=Select the location of DCS's Saved Games folder
@ -20,511 +21,58 @@ SelectDirLabel3=DCS Olympus must be installed within DCS's Saved Games folder.
SelectDirBrowseLabel=This is the detected path. If this is incorrect, click Browse to set the correct folder.
[Tasks]
; NOTE: The following entry contains English phrases ("Create a desktop icon" and "Additional icons"). You are free to translate them into another language if required.
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "desktopicon"; Description: "Create desktop shortcut"; GroupDescription: "Additional icons"; Flags: unchecked
Name: "installmodules"; Description: "Install node.js modules"; GroupDescription: "Dependencies";
[Files]
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
Source: "..\olympus.json"; DestDir: "{app}\Mods\Services\Olympus"; Flags: onlyifdoesntexist
Source: "..\olympus.json"; DestDir: "{app}";
Source: "..\scripts\OlympusHook.lua"; DestDir: "{app}\Scripts\Hooks"; Flags: ignoreversion
Source: "..\scripts\OlympusCommand.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion
Source: "..\scripts\unitPayloads.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion
Source: "..\scripts\templates.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion
Source: "..\scripts\mist.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion
Source: "..\scripts\mods.lua"; DestDir: "{app}\Mods\Services\Olympus\Scripts"; Flags: ignoreversion
Source: "..\scripts\OlympusHook.lua"; DestDir: "{app}\Scripts"; Flags: ignoreversion
Source: "..\scripts\OlympusCommand.lua"; DestDir: "{app}\mod\scripts"; Flags: ignoreversion
Source: "..\scripts\unitPayloads.lua"; DestDir: "{app}\mod\scripts"; Flags: ignoreversion
Source: "..\scripts\templates.lua"; DestDir: "{app}\mod\scripts"; Flags: ignoreversion
Source: "..\scripts\mist.lua"; DestDir: "{app}\mod\scripts"; Flags: ignoreversion
Source: "..\scripts\mods.lua"; DestDir: "{app}\mod\scripts"; Flags: ignoreversion
Source: "..\mod\*"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion recursesubdirs;
Source: "..\mod\*"; DestDir: "{app}\mod"; Flags: ignoreversion recursesubdirs;
Source: "..\bin\*.dll"; DestDir: "{app}\mod\bin"; Flags: ignoreversion;
Source: "..\client\public\databases\*"; DestDir: "{app}\mod\databases"; Flags: ignoreversion recursesubdirs;
Source: "..\bin\*.dll"; DestDir: "{app}\Mods\Services\Olympus\bin"; Flags: ignoreversion;
Source: "..\client\bin\*"; DestDir: "{app}\client\bin"; Flags: ignoreversion;
Source: "..\client\public\*"; DestDir: "{app}\client\public"; Flags: ignoreversion recursesubdirs;
Source: "..\client\routes\*"; DestDir: "{app}\client\routes"; Flags: ignoreversion recursesubdirs;
Source: "..\client\views\*"; DestDir: "{app}\client\views"; Flags: ignoreversion recursesubdirs;
Source: "..\client\app.js"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\demo.js"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\client.js"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\package.json"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\configurator.js"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\install.bat"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\*.vbs"; DestDir: "{app}\client"; Flags: ignoreversion;
Source: "..\client\bin\*"; DestDir: "{app}\Mods\Services\Olympus\client\bin"; Flags: ignoreversion;
Source: "..\client\public\*"; DestDir: "{app}\Mods\Services\Olympus\client\public"; Flags: ignoreversion recursesubdirs;
Source: "..\client\routes\*"; DestDir: "{app}\Mods\Services\Olympus\client\routes"; Flags: ignoreversion recursesubdirs;
Source: "..\client\views\*"; DestDir: "{app}\Mods\Services\Olympus\client\views"; Flags: ignoreversion recursesubdirs;
Source: "..\client\app.js"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
Source: "..\client\demo.js"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
Source: "..\client\package.json"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
Source: "..\client\run_client.js"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
Source: "..\manager\icons\*"; DestDir: "{app}\manager\icons"; Flags: ignoreversion;
Source: "..\manager\ejs\*"; DestDir: "{app}\manager\ejs"; Flags: ignoreversion;
Source: "..\manager\javascripts\*"; DestDir: "{app}\manager\javascripts"; Flags: ignoreversion;
Source: "..\manager\stylesheets\*"; DestDir: "{app}\manager\stylesheets"; Flags: ignoreversion;
Source: "..\manager\*"; DestDir: "{app}\manager"; Flags: ignoreversion;
Source: "..\client\configurator.js"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
Source: "..\client\install.bat"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion;
Source: "..\img\olympus.ico"; DestDir: "{app}\img"; Flags: ignoreversion;
Source: "..\img\olympus_server.ico"; DestDir: "{app}\img"; Flags: ignoreversion;
Source: "..\img\olympus_configurator.ico"; DestDir: "{app}\img"; Flags: ignoreversion;
Source: "..\img\configurator_logo.png"; DestDir: "{app}\img"; Flags: ignoreversion;
Source: "..\img\OlympusLogoFinal_4k.png"; DestDir: "{app}\img"; Flags: ignoreversion;
Source: "..\img\olympus.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "..\img\olympus_server.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "..\img\olympus_configurator.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "..\img\configurator_logo.png"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "..\LEGAL.txt"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion;
Source: "..\LEGAL.txt"; DestDir: "{app}"; Flags: ignoreversion;
[Run]
Filename: "{app}\Mods\Services\Olympus\client\install.bat"; WorkingDir:"{app}\Mods\Services\Olympus\client"; Flags: runhidden; StatusMsg: "Installing node.js modules, this may take some time...";
Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; Parameters: configurator.js -a {code:GetAddress} -c {code:GetClientPort} -b {code:GetBackendPort} -p {code:GetPassword} --bp {code:GetBluePassword} --rp {code:GetRedPassword}; Check: CheckCallConfigurator; Flags: runhidden; StatusMsg: "Applying configuration...";
Filename: "{app}\client\install.bat"; Description: "Installing node.js modules, this may take some time..."; Tasks: installmodules;
Filename: "{app}\manager\install.bat"; Description: "Installing node.js modules, this may take some time..."; Tasks: installmodules;
Filename: "{app}\manager\manager.vbs"; WorkingDir: "{app}\manager"; Description: "Launch the Olympus manager"; Flags: postinstall shellexec;
[Icons]
Name: "{userdesktop}\DCS Olympus Client"; Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"; Check: CheckLocalInstall; Parameters: "run_client.js"; Flags: runminimized;
Name: "{userdesktop}\DCS Olympus Server"; Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_server.ico"; Parameters: ".\bin\www";
Name: "{userdesktop}\DCS Olympus Configurator"; Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_configurator.ico"; Check: CheckServerInstall; Parameters: "configurator.js";
Name: "{app}\Mods\Services\Olympus\DCS Olympus Client"; Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"; Check: CheckLocalInstall; Parameters: "run_client.js"; Flags: runminimized;
Name: "{app}\Mods\Services\Olympus\DCS Olympus Server"; Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_server.ico"; Parameters: ".\bin\www";
Name: "{app}\Mods\Services\Olympus\DCS Olympus Configurator"; Filename: "node.exe"; WorkingDir:"{app}\Mods\Services\Olympus\client"; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_configurator.ico"; Check: CheckServerInstall; Parameters: "configurator.js";
Name: "{userdesktop}\DCS Olympus Manager"; Filename: "{app}\manager\manager.vbs"; Tasks: desktopicon; IconFilename: "{app}\img\olympus_configurator.ico";
Name: "{app}\DCS Olympus Manager"; Filename: "{app}\manager\manager.vbs"; IconFilename: "{app}\img\olympus_configurator.ico";
[UninstallDelete]
Type: filesandordirs; Name: "{app}\Mods\Services\Olympus"
[Code]
var
lblLocalInstall: TLabel;
lblLocalInstallInstructions: TNewStaticText;
lblServerInstall: TLabel;
lblServerInstallInstructions: TNewStaticText;
lblKeepOld: TLabel;
lblClientPort: TLabel;
lblBackendPort: TLabel;
lblPassword: TLabel;
lblBluePassword: TLabel;
lblRedPassword: TLabel;
radioLocalInstall: TNewRadioButton;
radioServerInstall: TNewRadioButton;
checkKeepOld: TNewCheckBox;
txtClientPort: TEdit;
txtBackendPort: TEdit;
txtPassword: TPasswordEdit;
txtBluePassword: TPasswordEdit;
txtRedPassword: TPasswordEdit;
InstallationTypePage: Integer;
PasswordPage: Integer;
lblPasswordInstructions: TNewStaticText;
procedure AcceptNumbersOnlyKeyPress(Sender: TObject; var Key: Char);
var
KeyCode: Integer;
begin
// allow only numbers
KeyCode := Ord(Key);
if not ((KeyCode = 8) or ((KeyCode >= 48) and (KeyCode <= 57))) then
Key := #0;
end;
procedure frmAddress_Activate(Page: TWizardPage);
begin
end;
function frmAddress_ShouldSkipPage(Page: TWizardPage): Boolean;
begin
Result := False;
end;
function frmAddress_BackButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
function frmAddress_NextButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
procedure frmAddress_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean);
begin
end;
function frmInstallationType_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(
PreviousPageId,
'DCS Olympus configuration',
'Select installation type'
);
{ lblLocalInstall }
lblLocalInstall := TLabel.Create(Page);
with lblLocalInstall do
begin
Parent := Page.Surface;
Left := ScaleX(30);
Top := ScaleY(14);
Width := ScaleX(35);
Height := ScaleY(10);
Font.Style := [fsBold];
Caption := 'Local installation';
end;
{ lblLocalInstallInstructions }
lblLocalInstallInstructions := TNewStaticText.Create(Page);
with lblLocalInstallInstructions do
begin
Parent := Page.Surface;
Left := ScaleX(30);
Top := ScaleY(31);
Width := ScaleX(340);
Height := ScaleY(23);
WordWrap := True;
Caption := 'Select this to install DCS Olympus locally. DCS Olympus will not be reachable by external clients (i.e. browsers running on different PCs)';
end;
{ radioLocalInstall }
radioLocalInstall := TNewRadioButton.Create(Page);
with radioLocalInstall do
begin
Parent := Page.Surface;
Left := ScaleX(10);
Top := ScaleY(12);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 0;
Checked := True
end;
{ lblServerInstall }
lblServerInstall := TLabel.Create(Page);
with lblServerInstall do
begin
Parent := Page.Surface;
Left := ScaleX(30);
Top := ScaleY(76);
Width := ScaleX(52);
Height := ScaleY(13);
Font.Style := [fsBold];
Caption := 'Dedicated server installation';
end;
{ lblServerInstallInstructions }
lblServerInstallInstructions := TNewStaticText.Create(Page);
with lblServerInstallInstructions do
begin
Parent := Page.Surface;
Left := ScaleX(30);
Top := ScaleY(93);
Width := ScaleX(340);
Height := ScaleY(13);
WordWrap := True;
Caption := 'Select this to install DCS Olympus on a dedicated server. DCS Olympus will be reachable by external clients. NOTE: to enable external connections, TCP port forwarding must be enabled on the selected ports.';
end;
{ radioServerInstall }
radioServerInstall := TNewRadioButton.Create(Page);
with radioServerInstall do
begin
Parent := Page.Surface;
Left := ScaleX(10);
Top := ScaleY(72);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 1;
end;
with Page do
begin
OnActivate := @frmAddress_Activate;
OnShouldSkipPage := @frmAddress_ShouldSkipPage;
OnBackButtonClick := @frmAddress_BackButtonClick;
OnNextButtonClick := @frmAddress_NextButtonClick;
OnCancelButtonClick := @frmAddress_CancelButtonClick;
end;
Result := Page.ID;
end;
procedure frmPassword_Activate(Page: TWizardPage);
begin
checkKeepOld.Enabled := FileExists(ExpandConstant('{app}\Mods\Services\Olympus\olympus.json'));
checkKeepOld.Checked := FileExists(ExpandConstant('{app}\Mods\Services\Olympus\olympus.json'));
end;
function frmPassword_ShouldSkipPage(Page: TWizardPage): Boolean;
begin
Result := False;
end;
function frmPassword_BackButtonClick(Page: TWizardPage): Boolean;
begin
Result := True;
end;
function frmPassword_NextButtonClick(Page: TWizardPage): Boolean;
begin
if checkKeepOld.Checked or ((Trim(txtClientPort.Text) <> '') and (Trim(txtBackendPort.Text) <> '') and (Trim(txtPassword.Text) <> '') and (Trim(txtBluePassword.Text) <> '') and (Trim(txtRedPassword.Text) <> '')) then begin
Result := True;
end else
begin
MsgBox('Either keep the configuration from the previous installation (if present) or fill all the fields to continue.', mbInformation, MB_OK);
Result := False;
end;
end;
procedure frmPassword_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean);
begin
end;
procedure checkKeepOldOnChange(Sender: TObject);
begin
txtPassword.Enabled := not checkKeepOld.Checked;
txtBluePassword.Enabled := not checkKeepOld.Checked;
txtRedPassword.Enabled := not checkKeepOld.Checked;
txtBackendPort.Enabled := not checkKeepOld.Checked;
txtClientPort.Enabled := not checkKeepOld.Checked;
end;
function frmPassword_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(
PreviousPageId,
'DCS Olympus passwords',
'Set DCS Olympus ports and passwords'
);
{ lblKeepOld }
lblKeepOld := TLabel.Create(Page);
with lblKeepOld do
begin
Parent := Page.Surface;
Left := ScaleX(54);
Top := ScaleY(0);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Keep configuration from previous installation';
end;
{ checkKeepOld }
checkKeepOld := TNewCheckBox.Create(Page);
with checkKeepOld do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(0);
Width := ScaleX(46);
Height := ScaleY(13);
OnClick := @checkKeepOldOnChange;
end;
{ lblPassword }
lblPassword := TLabel.Create(Page);
with lblPassword do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(38);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Game Master password';
end;
{ txtPassword }
txtPassword := TPasswordEdit.Create(Page);
with txtPassword do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(35);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 1;
end;
{ lblBluePassword }
lblBluePassword := TLabel.Create(Page);
with lblBluePassword do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(66);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Blue Commander password';
end;
{ txtBluePassword }
txtBluePassword := TPasswordEdit.Create(Page);
with txtBluePassword do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(63);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 2;
end;
{ lblRedPassword }
lblRedPassword := TLabel.Create(Page);
with lblRedPassword do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(94);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Red Commander password';
end;
{ txtRedPassword }
txtRedPassword := TPasswordEdit.Create(Page);
with txtRedPassword do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(91);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 3;
end;
{ lblClientPort }
lblClientPort := TLabel.Create(Page);
with lblClientPort do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(122);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Webserver port';
end;
{ txtClientPort }
txtClientPort := TEdit.Create(Page);
with txtClientPort do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(119);
Width := ScaleX(185);
Height := ScaleY(21);
Text := '3000';
OnKeyPress := @AcceptNumbersOnlyKeyPress;
TabOrder := 4;
end;
{ lblBackendPort }
lblBackendPort := TLabel.Create(Page);
with lblBackendPort do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(149);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Backend port';
end;
{ txtBackendPort }
txtBackendPort := TEdit.Create(Page);
with txtBackendPort do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(147);
Width := ScaleX(185);
Height := ScaleY(21);
Text := '3001';
OnKeyPress := @AcceptNumbersOnlyKeyPress;
TabOrder := 5;
end;
{ lblPasswordInstructions }
lblPasswordInstructions := TNewStaticText.Create(Page);
with lblPasswordInstructions do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(180);
Width := ScaleX(340);
Height := ScaleY(13);
WordWrap := True;
Caption := 'Passwords and ports can be changed in the future by using the DCS Olympus configurator. For more information, see the DCS Olympus Wiki.';
end;
with Page do
begin
OnActivate := @frmPassword_Activate;
OnShouldSkipPage := @frmPassword_ShouldSkipPage;
OnBackButtonClick := @frmPassword_BackButtonClick;
OnNextButtonClick := @frmPassword_NextButtonClick;
OnCancelButtonClick := @frmPassword_CancelButtonClick;
end;
Result := Page.ID;
end;
procedure InitializeWizard();
begin
{this page will come after welcome page}
InstallationTypePage := frmInstallationType_CreatePage(wpSelectDir);
PasswordPage := frmPassword_CreatePage(InstallationTypePage);
end;
function CheckLocalInstall(): boolean;
begin
if radioLocalInstall.Checked then begin
Result := True
end else
begin
Result := False
end
end;
function CheckServerInstall(): boolean;
begin
if radioLocalInstall.Checked then begin
Result := False
end else
begin
Result := True
end
end;
function CheckCallConfigurator(): boolean;
begin
if checkKeepOld.Checked then begin
Result := False
end else
begin
Result := True
end
end;
function GetAddress(Value: string): string;
begin
if radioLocalInstall.Checked then begin
Result := 'localhost'
end else
begin
Result := '*'
end
end;
function GetClientPort(Value: string): string;
begin
Result := txtClientPort.Text;
end;
function GetBackendPort(Value: string): string;
begin
Result := txtBackendPort.Text;
end;
function GetPassword(Value: string): string;
begin
Result := txtPassword.Text;
end;
function GetBluePassword(Value: string): string;
begin
Result := txtBluePassword.Text;
end;
function GetRedPassword(Value: string): string;
begin
Result := txtRedPassword.Text;
end;
Type: filesandordirs; Name: "{app}"

13
manager/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"runtimeExecutable": "npm",
"runtimeArgs": [ "start" ],
"port": 9229
}
]
}

View File

@ -0,0 +1,68 @@
<div class="instance-div">
<div class="folder-name">
<div class="icon folder"></div>
<span><%= folder %></span>
</div>
<div class="version">
Detected .dll version: <span class="<%= version === "n/a"? '' : (version === newVersion? 'accent-green': 'accent-red blink')%>"><%= version %></span> <br>
Available .dll version: <span class="accent-green"><%= newVersion %></span>
</div>
<div class="instance-content">
<table class="input-table">
<tr>
<td>
<div>
<div class="label">Client port <div class="icon info" title="This is the port that will allow users to connect to Olympus and must be forwarded in your firewall."></div></div>
<input id="client-port" type='number' value="<%= typeof client !== 'undefined' ? client["port"]: "" %>" min="1023" max="65535" <%= !installed? "disabled": "" %> tabindex="<%= index %>">
<span id="client-port-error" class="error"></span>
</div>
</td>
<td>
<div>
<div class="label">Game Master password <div class="icon info" title="This is the password you need to input to connect with the Game Master role."></div></div>
<input id="game-master-password" type="password" <%= !installed? "disabled": "" %> tabindex="<%= index + 3 %>">
<span id="game-master-password-error" class="error"></span>
</div>
</td>
</tr>
<tr>
<td>
<div>
<div class="label">Backend port <div class="icon info" title="This is the backend port used by Olympus to connect to DCS. You don't need to forward it in your firewall anymore."></div></div>
<input id="backend-port" type='number' value="<%= typeof server !== 'undefined' ? server["port"]: "" %>" min="1023" max="65535" <%= !installed? "disabled": "" %> tabindex="<%= index + 1 %>">
<span id="backend-port-error" class="error"></span>
</div>
</td>
<td>
<div>
<div class="label">Blue Commander password <div class="icon info" title="This is the password you need to input to connect with the Blue Commander role."></div></div>
<input id="blue-commander-password" type="password" <%= !installed? "disabled": "" %> tabindex="<%= index + 4 %>">
<span id="blue-commander-password-error" class="error"></span>
</div>
</td>
</tr>
<tr>
<td>
<div>
<div class="label">Backend address <div class="icon info" title="This is the address that DCS will listen on. Unless you want to send direct API commands, leave this to localhost even for dedicated server installations."></div></div>
<input id="backend-address" type='text' value="<%= typeof server !== 'undefined' ? server["address"]: "" %>" min="1023" max="65535" <%= !installed? "disabled": "" %> tabindex="<%= index + 2 %>">
<span id="backend-address-error" class="error"></span>
</div>
</td>
<td>
<div>
<div class="label">Red Commander password <div class="icon info" title="This is the password you need to input to connect with the Red Commander role."></div></div>
<input id="red-commander-password" type="password" <%= !installed? "disabled": "" %> tabindex="<%= index + 5 %>">
<span id="red-commander-password-error" class="error"></span>
</div>
</td>
</tr>
</table>
<div class="action-buttons">
<button class="button add <%= installed? "hide": "" %>" tabindex="<%= index + 6 %>" title="Clicking on this will install all the necessary files in your DCS instance. Remember to close DCS before doing this!">Install Olympus to instance</button>
<button class="button apply <%= !installed? "hide": "" %>" tabindex="<%= index + 7 %>" title="Clicking on this will apply your changes to the configuration. Remember to restart any running mission!">Apply changes</button>
<button class="button remove <%= !installed? "hide": "" %>" tabindex="<%= index + 8 %>" title="Clicking on this will remove Olympus from your DCS folder.">Remove Olympus</button>
<button class="button update <%= !(version !== "n/a" && version !== newVersion)? "hide": "" %>" tabindex="<%= index + 9 %>" title="Clicking on this will update Olympus in your DCS folder.">Update</button>
</div>
</div>
</div>

10
manager/ejs/popup.ejs Normal file
View File

@ -0,0 +1,10 @@
<div class="popup-header">
Information
</div>
<div class="popup-content">
<%= message %>
</div>
<div class="popup-footer">
<button class="button other <%= otherButton? "": " hide"%>" ><%= otherButton %></button>
<button class="button apply">Close</button>
</div>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="#F2F2F2" d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="#F2F2F2" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 521 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="#F2F2F2" d="M88.7 223.8L0 375.8V96C0 60.7 28.7 32 64 32H181.5c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7H416c35.3 0 64 28.7 64 64v32H144c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224H544c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480H32c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z"/></svg>

After

Width:  |  Height:  |  Size: 614 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="#F2F2F2" d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"/></svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M463.5 224H472c13.3 0 24-10.7 24-24V72c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8H463.5z"/></svg>

After

Width:  |  Height:  |  Size: 639 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path fill="#F2F2F2" d="M170.5 51.6L151.5 80h145l-19-28.4c-1.5-2.2-4-3.6-6.7-3.6H177.1c-2.7 0-5.2 1.3-6.7 3.6zm147-26.6L354.2 80H368h48 8c13.3 0 24 10.7 24 24s-10.7 24-24 24h-8V432c0 44.2-35.8 80-80 80H112c-44.2 0-80-35.8-80-80V128H24c-13.3 0-24-10.7-24-24S10.7 80 24 80h8H80 93.8l36.7-55.1C140.9 9.4 158.4 0 177.1 0h93.7c18.7 0 36.2 9.4 46.6 24.9zM80 128V432c0 17.7 14.3 32 32 32H336c17.7 0 32-14.3 32-32V128H80zm80 64V400c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16s16 7.2 16 16zm80 0V400c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16s16 7.2 16 16zm80 0V400c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16s16 7.2 16 16z"/></svg>

After

Width:  |  Height:  |  Size: 896 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M.3 89.5C.1 91.6 0 93.8 0 96V224 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64V224 96c0-35.3-28.7-64-64-64H64c-2.2 0-4.4 .1-6.5 .3c-9.2 .9-17.8 3.8-25.5 8.2C21.8 46.5 13.4 55.1 7.7 65.5c-3.9 7.3-6.5 15.4-7.4 24zM48 224H464l0 192c0 8.8-7.2 16-16 16L64 432c-8.8 0-16-7.2-16-16l0-192z"/></svg>

After

Width:  |  Height:  |  Size: 568 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M24 432c-13.3 0-24 10.7-24 24s10.7 24 24 24H488c13.3 0 24-10.7 24-24s-10.7-24-24-24H24z"/></svg>

After

Width:  |  Height:  |  Size: 368 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M432 48H208c-17.7 0-32 14.3-32 32V96H128V80c0-44.2 35.8-80 80-80H432c44.2 0 80 35.8 80 80V304c0 44.2-35.8 80-80 80H416V336h16c17.7 0 32-14.3 32-32V80c0-17.7-14.3-32-32-32zM48 448c0 8.8 7.2 16 16 16H320c8.8 0 16-7.2 16-16V256H48V448zM64 128H320c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V192c0-35.3 28.7-64 64-64z"/></svg>

After

Width:  |  Height:  |  Size: 621 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="12" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="#F2F2F2" d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>

After

Width:  |  Height:  |  Size: 560 B

72
manager/index.html Normal file
View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./stylesheets/style.css">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;600;700;800&display=swap" />
<meta charset="UTF-8">
<title>DCS Olympus Manager {{OLYMPUS_VERSION_NUMBER}}</title>
</head>
<body>
<div id="title-bar">
<div>DCS Olympus manager</div>
<button class="title-bar-button minimize"></button>
<button class="title-bar-button restore hide"></button>
<button class="title-bar-button maximize"></button>
<button class="title-bar-button close"></button>
</div>
<div id="header">
<img class="main-icon" src="../img/OlympusLogoFinal_4k.png" \>
<div>
<div> DCS Olympus Manager</div>
<div class="accent-green">{{OLYMPUS_VERSION_NUMBER}}</div>
</div>
<div>
These are the DCS instances that the Olympus Manager has automatically detected in your system. <br>
You can install Olympus and manage your instances from here. <br>
Click on "Install Olympus to instance" to install Olympus.
</div>
</div>
<div id="main-div">
</div>
<div id="footer">
</div>
</body>
<script>
document.querySelector('.minimize').addEventListener('click', () => {
window.ipcRender.send('window:minimize');
});
document.querySelector('.restore').addEventListener('click', () => {
window.ipcRender.send('window:restore');
});
document.querySelector('.maximize').addEventListener('click', () => {
window.ipcRender.send('window:maximize');
});
document.querySelector('.close').addEventListener('click', () => {
window.ipcRender.send('window:close');
});
window.ipcRender.receive('event:maximized', () => {
document.querySelector('.restore').classList.remove("hide");
document.querySelector('.maximize').classList.add("hide");
})
window.ipcRender.receive('event:unmaximized', () => {
document.querySelector('.restore').classList.add("hide");
document.querySelector('.maximize').classList.remove("hide");
})
</script>
</html>

1
manager/install.bat Normal file
View File

@ -0,0 +1 @@
npm install --omit=dev

View File

@ -0,0 +1,463 @@
var regedit = require('regedit')
var fs = require('fs')
var path = require('path')
const ejs = require('ejs')
const portfinder = require('portfinder')
const sha256 = require('sha256')
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
const createShortcut = require('create-desktop-shortcuts');
const vi = require('win-version-info');
const shellFoldersKey = 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders'
const saveGamesKey = '{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}'
var instanceDivs = [];
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'window:minimize', // Channel names
'window:maximize',
'window:restore',
'window:close'
],
// From main to render.
'receive': [
'event:maximized',
'event:unmaximized'
],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
function showPopup(message, otherButton, otherButtonCallback) {
var data = {
message: message,
otherButton: otherButton
};
var popups = document.querySelectorAll(".popup");
for (let i = 0; i < popups.length; i++) {
document.body.removeChild(popups[i])
}
ejs.renderFile("./ejs/popup.ejs", data, {}, (err, str) => {
var div = document.createElement("div");
div.classList.add("popup");
div.innerHTML = str;
document.body.appendChild(div);
div.querySelector(".apply").addEventListener("click", () => {
document.body.removeChild(div);
})
div.querySelector(".other").addEventListener("click", () => {
otherButtonCallback();
})
});
}
function checkPort(port, callback) {
portfinder.getPort({ port: port, stopPort: port }, (err, res) => {
if (err !== null) {
console.error(`Port ${port} already in use`);
callback(false);
} else {
callback(true);
}
});
}
function installOlympus(folder) {
console.log(`Installing Olympus in ${folder}`);
try {
fs.cpSync(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), { recursive: true });
fs.cpSync(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hook", "OlympusHook.lua"));
fs.cpSync(path.join("..", "olympus.json"), path.join(folder, "Config", "olympus.json"));
if (createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
outputPath: folder,
name: "DCS Olympus Client",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
}) &&
createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
outputPath: folder,
name: "DCS Olympus Server",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus_server.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
})) {
console.log("Shorcuts created succesfully")
} else {
return false;
}
} catch (e) {
console.error(e);
return false;
}
loadDivs();
return true;
}
function uninstallOlympus(folder) {
console.log(`Uninstalling Olympus from ${folder}`);
try {
fs.rmSync(path.join(folder, "Mods", "Services", "Olympus"), { recursive: true });
fs.rmSync(path.join(folder, "Config", "olympus.json"));
loadDivs();
} catch (e) {
console.error(e);
return false;
}
return true;
}
function applyConfiguration(folder, data) {
console.log(`Applying configuration to Olympus from ${folder}`);
if (fs.existsSync(path.join(folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(folder, "Config", "olympus.json")));
config["client"]["port"] = data["clientPort"];
config["server"]["port"] = data["backendPort"];
config["server"]["address"] = data["backendAddress"];
config["authentication"]["gameMasterPassword"] = sha256(data["gameMasterPassword"]);
config["authentication"]["blueCommanderPassword"] = sha256(data["blueCommanderPassword"]);
config["authentication"]["redCommanderPassword"] = sha256(data["redCommanderPassword"]);
try {
fs.writeFileSync(path.join(folder, "Config", "olympus.json"), JSON.stringify(config, null, 4));
} catch (e) {
console.error(e);
return false;
}
} else {
return false;
}
}
function updateOlympus(folder) {
console.log(`Updating Olympus in ${folder}`);
try {
fs.cpSync(path.join("..", "mod"), path.join(folder, "Mods", "Services", "Olympus"), { recursive: true });
fs.cpSync(path.join("..", "scripts", "OlympusHook.lua"), path.join(folder, "Scripts", "Hook", "OlympusHook.lua"));
loadDivs();
} catch (e) {
console.error(e);
return false;
}
return true;
}
function createDesktopShortcuts(folder) {
if (createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'client.vbs'),
name: "DCS Olympus Client",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
}) && createShortcut({
windows: {
filePath: path.resolve(__dirname, '..', '..', 'client', 'server.vbs'),
name: "DCS Olympus Server",
arguments: `"${path.join(folder, "Config", "olympus.json")}"`,
icon: path.resolve(__dirname, '..', '..', 'img', 'olympus_server.ico'),
workingDirectory: path.resolve(__dirname, '..', '..', 'client')
}
})) {
showPopup("Shortcuts created successfully!")
} else {
showPopup("And error occurred while creating the shortcuts.")
}
}
class InstanceDiv {
element = null;
parent = null;
folder = "";
constructor(parent, folder) {
this.element = parent;
this.folder = folder;
this.render();
}
render() {
this.element = document.createElement("div");
var data = {
folder: this.folder,
installed: false,
index: instanceDivs.length * 10
};
var newVersionInfo = vi(path.join("..", "mod", "bin", "olympus.dll"));
data["newVersion"] = newVersionInfo.ProductVersion;
data["version"] = "n/a";
if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) {
var config = JSON.parse(fs.readFileSync(path.join(this.folder, "Config", "olympus.json")));
data = {
...data,
...config
}
data["installed"] = true;
try {
data["version"] = vi(path.join(this.folder, "Mods", "Services", "Olympus", "bin", "olympus.dll")).ProductVersion;
} catch (e) {
data["version"] = "n/a";
}
}
ejs.renderFile("./ejs/instanceDiv.ejs", data, {}, (err, str) => {
this.element.innerHTML = str;
this.element.querySelector(".add").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being installed");
window.setTimeout(() => {
if (installOlympus(this.folder)) {
showPopup("Olympus installed successfully. Use the provided form to set Olympus properties. All fields are mandatory. Click on \"Create desktop shortcuts\" to generate Olympus shortcuts on your desktop.", "Create desktop shortcuts", () => {
createDesktopShortcuts(this.folder);
});
} else {
showPopup("An error has occurred during installation");
}
}, 100);
}
});
this.element.querySelector(".remove").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being uninstalled from DCS instance");
window.setTimeout(() => {
if (uninstallOlympus(this.folder)) {
showPopup("Olympus uninstalled successfully from DCS instance!");
} else {
showPopup("An error has occurred during uninstallation");
}
}, 100);
}
});
this.element.querySelector(".apply").addEventListener("click", (e) => {
e.srcElement.classList.remove("blink");
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while the configuration is being applied");
window.setTimeout(() => {
if (applyConfiguration(this.folder, this.getFields())) {
showPopup("Olympus configuration applied successfully!");
} else {
showPopup("An error has occurred while applying the configuration");
}
}, 100)
}
});
this.element.querySelector(".update").addEventListener("click", (e) => {
if (!e.srcElement.classList.contains("disabled")) {
showPopup("Please wait while Olympus is being updated in the DCS instance");
window.setTimeout(() => {
if (updateOlympus(this.folder)) {
showPopup("Olympus updated successfully from DCS instance!");
} else {
showPopup("An error has occurred during the update");
}
}, 100);
}
});
var inputs = this.element.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) {
inputs[i].addEventListener("change", () => {
inputs[i].classList.remove("error");
instanceDivs.forEach((instanceDiv) => instanceDiv.checkFields())
})
}
});
}
getDiv() {
return this.element;
}
getFields() {
return {
clientPort: Number(this.element.querySelector("#client-port").value),
backendPort: Number(this.element.querySelector("#backend-port").value),
backendAddress: this.element.querySelector("#backend-address").value,
gameMasterPassword: this.element.querySelector("#game-master-password").value,
blueCommanderPassword: this.element.querySelector("#blue-commander-password").value,
redCommanderPassword: this.element.querySelector("#red-commander-password").value,
}
}
checkFields() {
var data = this.getFields();
/* Clear existing errors */
var inputs = this.element.querySelectorAll("input");
for (let i = 0; i < inputs.length; i++) {
inputs[i].classList.remove("error");
}
var messages = this.element.querySelectorAll(".error");
for (let i = 0; i < messages.length; i++) {
messages[i].innerText = "";
}
/* Enable the button */
this.element.querySelector(".apply").classList.remove("disabled");
if (data["clientPort"] !== 0 && data["backendPort"] !== 0) {
if (data["clientPort"] === data["backendPort"]) {
this.element.querySelector("#client-port").classList.add("error");
this.element.querySelector("#client-port-error").innerText = "Ports must be different";
this.element.querySelector("#backend-port").classList.add("error");
this.element.querySelector("#backend-port-error").innerText = "Ports must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
else {
checkPort(data["clientPort"], (res) => {
var otherInstanceUsesPort = instanceDivs.find((instanceDiv) => {
if (instanceDiv != this) {
var fields = instanceDiv.getFields();
if (fields["clientPort"] === data["clientPort"] || fields["backendPort"] === data["clientPort"]) {
return true;
}
}
})
if (!res || otherInstanceUsesPort) {
this.element.querySelector("#client-port").classList.add("error");
this.element.querySelector("#client-port-error").innerText = "Port already in use";
this.element.querySelector(".apply").classList.add("disabled");
}
});
checkPort(data["backendPort"], (res) => {
var otherInstanceUsesPort = instanceDivs.find((instanceDiv) => {
if (instanceDiv != this) {
var fields = instanceDiv.getFields();
if (fields["clientPort"] === data["backendPort"] || fields["backendPort"] === data["backendPort"]) {
return true;
}
}
})
if (!res || otherInstanceUsesPort) {
this.element.querySelector("#backend-port").classList.add("error");
this.element.querySelector("#backend-port-error").innerText = "Port already in use";
this.element.querySelector(".apply").classList.add("disabled");
}
});
}
}
if (data["gameMasterPassword"] !== "" && data["blueCommanderPassword"] !== "" && data["gameMasterPassword"] === data["blueCommanderPassword"]) {
this.element.querySelector("#game-master-password").classList.add("error");
this.element.querySelector("#game-master-password-error").innerText = "Passwords must be different";
this.element.querySelector("#blue-commander-password").classList.add("error");
this.element.querySelector("#blue-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["gameMasterPassword"] !== "" && data["redCommanderPassword"] !== "" && data["gameMasterPassword"] === data["redCommanderPassword"]) {
this.element.querySelector("#game-master-password").classList.add("error");
this.element.querySelector("#game-master-password-error").innerText = "Passwords must be different";
this.element.querySelector("#red-commander-password").classList.add("error");
this.element.querySelector("#red-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["blueCommanderPassword"] !== "" && data["redCommanderPassword"] !== "" && data["blueCommanderPassword"] === data["redCommanderPassword"]) {
this.element.querySelector("#blue-commander-password").classList.add("error");
this.element.querySelector("#blue-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector("#red-commander-password").classList.add("error");
this.element.querySelector("#red-commander-password-error").innerText = "Passwords must be different";
this.element.querySelector(".apply").classList.add("disabled");
}
if (data["gameMasterPassword"] === "" || data["blueCommanderPassword"] === "" || data["redCommanderPassword"] === "") {
this.element.querySelector(".apply").classList.add("disabled");
}
}
}
function loadDivs() {
regedit.list(shellFoldersKey, function (err, result) {
if (err) {
console.log(err);
}
else {
if (result[shellFoldersKey] !== undefined && result[shellFoldersKey]["exists"] && result[shellFoldersKey]['values'][saveGamesKey] !== undefined && result[shellFoldersKey]['values'][saveGamesKey]['value'] !== undefined) {
const searchpath = result[shellFoldersKey]['values'][saveGamesKey]['value'];
const folders = fs.readdirSync(searchpath);
instanceDivs = [];
const mainDiv = document.getElementById("main-div");
folders.forEach((folder) => {
if (fs.existsSync(path.join(searchpath, folder, "Config", "appsettings.lua")) ||
fs.existsSync(path.join(searchpath, folder, "Config", "serversettings.lua"))) {
instanceDivs.push(new InstanceDiv(mainDiv, path.join(searchpath, folder)));
}
});
mainDiv.replaceChildren(...instanceDivs.map((instanceDiv) => {
return instanceDiv.getDiv();
}));
instanceDivs.forEach((instanceDiv) => instanceDiv.checkFields())
} else {
console.error("An error occured while trying to fetch the location of the DCS folders.")
}
}
})
}
window.addEventListener('DOMContentLoaded', () => {
loadDivs();
})

66
manager/manager.js Normal file
View File

@ -0,0 +1,66 @@
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const path = require('path');
let window;
function createWindow() {
const window = new electronBrowserWindow({
width: 1310,
height: 800,
frame: false,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, "javascripts", 'preload.js'),
nodeIntegration: true, // like here
},
icon: "./../img/olympus_configurator.ico"
});
window.loadFile('index.html').then(() => { window.show(); });
window.on("maximize", () => {
window.webContents.send('event:maximized')
})
window.on("unmaximize", () => {
window.webContents.send('event:unmaximized')
})
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ---
electronIpcMain.on('window:minimize', () => {
window.minimize();
})
electronIpcMain.on('window:maximize', () => {
window.maximize();
})
electronIpcMain.on('window:restore', () => {
window.restore();
})
electronIpcMain.on('window:close', () => {
window.close();
})

1
manager/manager.vbs Normal file
View File

@ -0,0 +1 @@
CreateObject("Wscript.Shell").Run "npm start", 0

21
manager/package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "dcsolympus_manager",
"version": "1.0.0",
"description": "",
"main": "manager.js",
"scripts": {
"start": "electron ."
},
"author": "",
"license": "ISC",
"dependencies": {
"create-desktop-shortcuts": "^1.10.1",
"create-windowless-app": "^11.0.0",
"ejs": "^3.1.9",
"electron": "^28.0.0",
"portfinder": "^1.0.32",
"regedit": "^5.1.2",
"sha256": "^0.2.0",
"win-version-info": "^6.0.1"
}
}

View File

@ -0,0 +1,353 @@
* {
font-family: "Open Sans", sans-serif;
box-sizing: border-box;
}
body {
background-color: #181e25;
padding: 0px;
margin: 0px;
}
#title-bar {
content: " ";
display: block;
-webkit-user-select: none;
-webkit-app-region: drag;
height: 20px;
width: 100%;
display: flex;
justify-content: end;
column-gap: 15px;
padding: 5px;
}
#title-bar>*:first-child {
margin-right: auto;
color: #F2F2F2AA;
font-size: 12px;
}
.title-bar-button {
background-color: transparent;
border: 0px solid transparent;
height: 20px;
width: 20px;
background-repeat: no-repeat;
background-position: 50% 50%;
cursor: pointer;
}
.minimize {
background-image: url("../icons/window-minimize-regular.svg");
}
.restore {
background-image: url("../icons/window-restore-regular.svg");
}
.maximize {
background-image: url("../icons/window-maximize-regular.svg");
}
.close {
background-image: url("../icons/xmark-solid.svg");
}
.title-bar-button {
-webkit-app-region: no-drag;
}
#header {
display: flex;
justify-content: start;
align-items: start;
color: #F2F2F2;
font-weight: bold;
font-size: 16px;
padding: 20px;
column-gap: 10px;
}
#header>div:first-of-type{
width: 300px;
}
#header>div:last-child {
font-size: 13px;
margin-left: 30px;
font-weight: 400;
text-align: right;
width: 100%;
}
.main-icon {
width: 60px;
height: 60px;
}
#main-div {
display: flex;
flex-direction: row;
height: 100%;
row-gap: 30px;
column-gap: 30px;
flex-wrap: wrap;
padding: 25px;
}
body {
overflow: auto;
scrollbar-color: white transparent;
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background-color: transparent;
border-bottom-right-radius: 10px;
border-top-right-radius: 10px;
margin-top: 0px;
}
::-webkit-scrollbar-thumb {
background-color: white;
border-radius: 100px;
margin-top: 10px;
opacity: 0.8;
}
.instance-div {
display: flex;
flex-direction: column;
row-gap: 10px;
background-color: #3d4651;
height: fit-content;
padding: 20px 40px;
border-radius: 5px;
border-left: 5px solid #017DC1;
width: 600px;
box-shadow: 0px 0px 5px #000A;
}
.instance-content {
display: flex;
flex-direction: column;
row-gap: 15px;
}
.folder-name {
color: #F2F2F2;
font-weight: bold;
display: flex;
border-bottom: 1px solid #F2F2F2;
padding-bottom: 10px;
width: 100%;
}
.folder-name span {
width: 500px;
overflow: hidden;
text-wrap: nowrap;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
}
.folder {
width: 20px;
height: 20px;
background-image: url("../icons/folder-open-solid.svg");
margin-right: 15px;
}
.version {
font-size: 13px;
color: #F2F2F2;
}
.input-table {
padding: 0px;
margin: 0px;
border-width: 0px;
border-collapse: collapse;
}
.input-table td {
color: #F2F2F2;
padding: 5px 0px;
font-size: 13px;
vertical-align: top;
}
.input-table td>div {
display: flex;
flex-direction: column;
width: fit-content;
}
.action-buttons {
display: flex;
flex-direction: row;
column-gap: 12px;
justify-content: start;
}
.label {
display: flex;
align-items: center;
column-gap: 5px;
}
.icon {
background-size: 100% 100%;
background-repeat: no-repeat;
}
.button {
width: fit-content;
height: 40px;
color: #F2F2F2;
border: 1px solid #F2F2F2;
background-size: 40px 60%;
background-position: 0px 50%;
background-repeat: no-repeat;
border-radius: 5px;
padding: 5px 15px 5px 45px;
font-weight: 600;
display: flex;
align-items: center;
font-size: 14px;
background-color: transparent;
}
.button:not(.disabled) {
cursor: pointer;
}
.apply {
background-image: url("../icons/check-solid.svg");
background-color: #017DC1;
border: 1px solid transparent;
}
.add {
padding: 5px 15px 5px 15px;
}
.update {
background-image: url("../icons/rotate-right-solid.svg");
}
.other {
padding: 5px 15px 5px 15px;
}
.remove {
background-image: url("../icons/trash-can-regular.svg");
}
.disabled {
background-color: #797E83;
}
.hide {
display: none;
}
.message {
font-weight: 600;
height: 20px;
font-size: 13px;
color: #F2F2F2;
}
input {
font-weight: 600;
font-size: 13px;
width: 240px;
border-radius: 4px;
}
input:focus{
outline: none;
}
input.error {
border-color: #FF5858;
}
.error {
font-weight: bold;
color: #FF5858;
}
.accent-red {
color: #FF5858;
}
.accent-green {
color: #8bff63;
}
.info {
width: 12px;
height: 12px;
background-image: url("../icons/circle-info-solid.svg");
background-position: 50% 50%;
}
.blink {
animation: blinker 1s linear infinite;
}
@keyframes blinker {
50% {
color: transparent;
}
}
.popup {
display: flex;
flex-direction: column;
row-gap: 15px;
height: 300px;
width: 500px;
position: absolute;
top: 50%;
left: 50%;
background-color: #181e25;
border-radius: 5px;
transform: translate(-250px, -150px);
padding: 30px;
box-shadow: 0px 0px 5px #000A;
}
.popup-header {
height: 20px;
color: #F2F2F2;
font-size: 14px;
font-weight: 600;
}
.popup-content {
height: calc(100% - 50px);
color: #F2F2F2;
font-size: 13px;
}
.popup-footer {
height: 30px;
display: flex;
justify-content: end;
column-gap: 15px;
}
.popup-footer .apply {
background-image: none;
padding: 5px 15px 5px 15px;
}

View File

@ -0,0 +1,19 @@
{
"server": {
"address": "localhost",
"port": 3001
},
"authentication": {
"gameMasterPassword": "4b8823ed9e5c2392ab4a791913bb8ce41956ea32e308b760eefb97536746dd33",
"blueCommanderPassword": "b0ea4230c1558c5313165eda1bdb7fced008ca7f2ca6b823fb4d26292f309098",
"redCommanderPassword": "302bcbaf2a3fdcf175b689bf102d6cdf9328f68a13d4096101bba806482bfed9"
},
"client": {
"port": 3000,
"elevationProvider": {
"provider": "https://srtm.fasma.org/{lat}{lng}.SRTMGL3S.hgt.zip",
"username": null,
"password": null
}
}
}

View File

@ -0,0 +1,466 @@
{
"airfields": {
"Anapa-Vityazevo": {
"ICAO": "URKA",
"elevation": "141",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "214",
"ILS": ""
},
"04": {
"magHeading": "034",
"ILS": ""
}
}
],
"length": "9000"
}
]
},
"Batumi": {
"ICAO": "UGSB",
"elevation": "33",
"TACAN": "16X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "119",
"ILS": ""
},
"31": {
"magHeading": "299",
"ILS": ""
}
}
],
"length": "7500"
}
]
},
"Beslan": {
"ICAO": "URMO",
"elevation": "1722",
"TACAN": "",
"runways": [
{
"headings": [
{
"10": {
"magHeading": "086",
"ILS": "110.50"
},
"28": {
"magHeading": "266",
"ILS": ""
}
}
],
"length": "9600"
}
]
},
"Gelendzhik": {
"ICAO": "URKG",
"elevation": "72",
"TACAN": "",
"runways": [
{
"headings": [
{
"19": {
"magHeading": "212",
"ILS": ""
},
"01": {
"magHeading": "032",
"ILS": ""
}
}
],
"length": "5400"
}
]
},
"Gudauta": {
"ICAO": "UG23",
"elevation": "69",
"TACAN": "",
"runways": [
{
"headings": [
{
"15": {
"magHeading": "144",
"ILS": ""
},
"33": {
"magHeading": "324",
"ILS": ""
}
}
],
"length": "7700"
}
]
},
"Kobuleti": {
"ICAO": "UG5X",
"elevation": "69",
"TACAN": "67X",
"runways": [
{
"headings": [
{
"25": {
"magHeading": "243",
"ILS": ""
},
"07": {
"magHeading": "063",
"ILS": "111.50"
}
}
],
"length": "7400"
}
]
},
"Krasnodar-Center": {
"ICAO": "URKL",
"elevation": "98",
"TACAN": "",
"runways": [
{
"headings": [
{
"27": {
"magHeading": "259",
"ILS": ""
},
"09": {
"magHeading": "079",
"ILS": ""
}
}
],
"length": "7700"
}
]
},
"Krasnodar-Pashkovsky": {
"ICAO": "URKK",
"elevation": "112",
"TACAN": "",
"runways": [
{
"headings": [
{
"23": {
"magHeading": "219",
"ILS": ""
},
"05": {
"magHeading": "039",
"ILS": ""
}
}
],
"length": "9600"
}
]
},
"Krymsk": {
"ICAO": "URKW",
"elevation": "66",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "212",
"ILS": ""
},
"04": {
"magHeading": "032",
"ILS": ""
}
}
],
"length": "8000"
}
]
},
"Kutaisi": {
"ICAO": "UGKO",
"elevation": "148",
"TACAN": "44X",
"runways": [
{
"headings": [
{
"25": {
"magHeading": "247",
"ILS": ""
},
"07": {
"magHeading": "067'",
"ILS": "109.75"
}
}
],
"length": "7700"
}
]
},
"Maykop-Khanskaya": {
"ICAO": "URKH",
"elevation": "591",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "211",
"ILS": ""
},
"04": {
"magHeading": "031",
"ILS": ""
}
}
],
"length": "10100"
}
]
},
"Mineralnye Vody": {
"ICAO": "URMM",
"elevation": "1050",
"TACAN": "",
"runways": [
{
"headings": [
{
"12": {
"magHeading": "108",
"ILS": "111.70"
},
"30": {
"magHeading": "288",
"ILS": "109.30"
}
}
],
"length": "12700"
}
]
},
"Mozdok": {
"ICAO": "XRMF",
"elevation": "507",
"TACAN": "",
"runways": [
{
"headings": [
{
"26": {
"magHeading": "255",
"ILS": ""
},
"08": {
"magHeading": "075",
"ILS": ""
}
}
],
"length": "9400"
}
]
},
"Nalchik": {
"ICAO": "URMN",
"elevation": "1411",
"TACAN": "",
"runways": [
{
"headings": [
{
"24": {
"magHeading": "228",
"ILS": "110.50"
},
"06": {
"magHeading": "048'",
"ILS": ""
}
}
],
"length": "7000"
}
]
},
"Novorossiysk": {
"ICAO": "URKN",
"elevation": "131",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "214",
"ILS": ""
},
"04": {
"magHeading": "034",
"ILS": ""
}
}
],
"length": "5400"
}
]
},
"Senaki-Kolkhi": {
"ICAO": "UGKS",
"elevation": "43",
"TACAN": "31X",
"runways": [
{
"headings": [
{
"27": {
"magHeading": "268",
"ILS": ""
},
"09": {
"magHeading": "088'",
"ILS": "108.90"
}
}
],
"length": "7400"
}
]
},
"Sochi-Adler": {
"ICAO": "URSS",
"elevation": "98",
"TACAN": "",
"runways": [
{
"headings": [
{
"27": {
"magHeading": "235",
"ILS": ""
},
"06": {
"magHeading": "055",
"ILS": "111.10"
}
}
],
"length": "9700"
}
]
},
"Tbilisi-Lochini": {
"ICAO": "UGTB",
"elevation": "1574",
"TACAN": "25X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "121",
"ILS": "110.30"
},
"31": {
"magHeading": "301",
"ILS": "108.90"
}
}
],
"length": "9300"
}
]
},
"Soganlug": {
"ICAO": "UG24",
"elevation": "1500",
"TACAN": "25X",
"runways": [
{
"headings": [
{
"14": {
"magHeading": "125",
"ILS": ""
},
"32": {
"magHeading": "305",
"ILS": ""
}
}
],
"length": "6500"
}
]
},
"Sukhumi-Babushara": {
"ICAO": "UGSS",
"elevation": "43",
"TACAN": "",
"runways": [
{
"headings": [
{
"12": {
"magHeading": "109",
"ILS": ""
},
"30": {
"magHeading": "289",
"ILS": ""
}
}
],
"length": "11400"
}
]
},
"Vaziani": {
"ICAO": "UG27",
"elevation": "1524",
"TACAN": "22X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "129",
"ILS": "108.75"
},
"31": {
"magHeading": "309",
"ILS": "108.75"
}
}
],
"length": "7700"
}
]
}
}
}

View File

@ -0,0 +1,838 @@
{
"airfields": {
"Aerodromo De Tolhuin": {
"runways": [
{
"headings": [
{
"07": {
"magHeading": "66",
"Heading": "77",
"ILS": ""
}
},
{
"25": {
"magHeading": "246",
"Heading": "257",
"ILS": ""
}
}
],
"length": "3297"
}
],
"TACAN": "",
"ICAO": "SAWL",
"elevation": "355"
},
"Almirante Schroeders": {
"runways": [
{
"headings": [
{
"12": {
"magHeading": "114",
"Heading": "127",
"ILS": ""
}
},
{
"30": {
"magHeading": "294",
"Heading": "307",
"ILS": ""
}
}
],
"length": "4628"
},
{
"headings": [
{
"22": {
"magHeading": "208",
"Heading": "221",
"ILS": ""
}
},
{
"04": {
"magHeading": "28",
"Heading": "41",
"ILS": ""
}
}
],
"length": "3931"
}
],
"TACAN": "",
"ICAO": "SCDW",
"elevation": "160"
},
"Rio Chico": {
"runways": [
{
"headings": [
{
"08": {
"magHeading": "73",
"Heading": "84",
"ILS": ""
}
},
{
"26": {
"magHeading": "253",
"Heading": "264",
"ILS": ""
}
}
],
"length": "3239"
}
],
"TACAN": "",
"ICAO": "RGR",
"elevation": "74"
},
"Puerto Natales": {
"runways": [
{
"headings": [
{
"28": {
"magHeading": "274",
"Heading": "287",
"ILS": ""
}
},
{
"10": {
"magHeading": "94",
"Heading": "107",
"ILS": ""
}
}
],
"length": "5281"
}
],
"TACAN": "",
"ICAO": "SCNT",
"elevation": "216"
},
"Mount Pleasant": {
"runways": [
{
"headings": [
{
"10": {
"magHeading": "101",
"Heading": "104",
"ILS": ""
}
},
{
"28": {
"magHeading": "281",
"Heading": "284",
"ILS": "111.90"
}
}
],
"length": "6763"
},
{
"headings": [
{
"23": {
"magHeading": "231",
"Heading": "234",
"ILS": ""
}
},
{
"05": {
"magHeading": "51",
"Heading": "54",
"ILS": ""
}
}
],
"length": "6763"
}
],
"TACAN": "59X",
"ICAO": "EGYP",
"elevation": "243"
},
"Aerodromo O'Higgins": {
"runways": [
{
"headings": [
{
"34": {
"magHeading": "326",
"Heading": "338",
"ILS": ""
}
},
{
"16": {
"magHeading": "146",
"Heading": "158",
"ILS": ""
}
}
],
"length": "3968"
}
],
"TACAN": "",
"ICAO": "SCOH",
"elevation": "900"
},
"Ushuaia Helo Port": {
"runways": [
{
"headings": [
{
"16": {
"magHeading": "157",
"Heading": "170",
"ILS": ""
}
},
{
"34": {
"magHeading": "337",
"Heading": "350",
"ILS": ""
}
}
],
"length": "5076"
}
],
"TACAN": "",
"ICAO": "SAWO",
"elevation": "42"
},
"Franco Bianco": {
"runways": [
{
"headings": [
{
"07": {
"magHeading": "65",
"Heading": "77",
"ILS": ""
}
},
{
"25": {
"magHeading": "245",
"Heading": "257",
"ILS": ""
}
}
],
"length": "5008"
},
{
"headings": [
{
"20": {
"magHeading": "185",
"Heading": "196",
"ILS": ""
}
},
{
"02": {
"magHeading": "5",
"Heading": "16",
"ILS": ""
}
}
],
"length": "5008"
}
],
"TACAN": "",
"ICAO": "SCSB",
"elevation": "104"
},
"Pampa Guanaco": {
"runways": [
{
"headings": [
{
"26": {
"magHeading": "248",
"Heading": "260",
"ILS": ""
}
},
{
"08": {
"magHeading": "68",
"Heading": "80",
"ILS": ""
}
}
],
"length": "2349"
}
],
"TACAN": "",
"ICAO": "SCBI",
"elevation": "519"
},
"Puerto Santa Cruz": {
"runways": [
{
"headings": [
{
"07": {
"magHeading": "81",
"Heading": "69",
"ILS": ""
}
},
{
"25": {
"magHeading": "261",
"Heading": "249",
"ILS": ""
}
}
],
"length": "5638"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "365"
},
"San Carlos FOB": {
"runways": [
{
"headings": [
{
"11": {
"magHeading": "285",
"Heading": "288",
"ILS": ""
}
},
{
"29": {
"magHeading": "105",
"Heading": "108",
"ILS": ""
}
}
],
"length": "1079"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "65"
},
"Goose Green": {
"runways": [
{
"headings": [
{
"14": {
"magHeading": "135",
"Heading": "139",
"ILS": ""
}
},
{
"32": {
"magHeading": "315",
"Heading": "319",
"ILS": ""
}
}
],
"length": "1976"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "57"
},
"Rio Turbio": {
"runways": [
{
"headings": [
{
"24": {
"magHeading": "225",
"Heading": "239",
"ILS": ""
}
},
{
"06": {
"magHeading": "45",
"Heading": "59",
"ILS": ""
}
}
],
"length": "5057"
}
],
"TACAN": "",
"ICAO": "SAWT",
"elevation": "895"
},
"Porvenir Airfield": {
"runways": [
{
"headings": [
{
"09": {
"magHeading": "87",
"Heading": "99",
"ILS": ""
}
},
{
"27": {
"magHeading": "266",
"Heading": "279",
"ILS": ""
}
}
],
"length": "7821"
},
{
"headings": [
{
"21": {
"magHeading": "203",
"Heading": "216",
"ILS": ""
}
},
{
"03": {
"magHeading": "23",
"Heading": "36",
"ILS": ""
}
}
],
"length": "3220"
}
],
"TACAN": "",
"ICAO": "SCFM",
"elevation": "56"
},
"Punta Arenas": {
"runways": [
{
"headings": [
{
"30": {
"magHeading": "295",
"Heading": "308",
"ILS": ""
}
},
{
"12": {
"magHeading": "140",
"Heading": "128",
"ILS": ""
}
}
],
"length": "6834"
},
{
"headings": [
{
"25": {
"magHeading": "270",
"Heading": "258",
"ILS": ""
}
},
{
"07": {
"magHeading": "90",
"Heading": "78",
"ILS": ""
}
}
],
"length": "9360"
},
{
"headings": [
{
"19": {
"magHeading": "210",
"Heading": "198",
"ILS": ""
}
}
],
"length": "5160"
}
],
"TACAN": "",
"ICAO": "SCCI",
"elevation": "122"
},
"Gull Point": {
"runways": [
{
"headings": [
{
"15": {
"magHeading": "149",
"Heading": "152",
"ILS": ""
}
},
{
"33": {
"magHeading": "329",
"Heading": "332",
"ILS": ""
}
}
],
"length": "1521"
},
{
"headings": [
{
"23": {
"magHeading": "212",
"Heading": "216",
"ILS": ""
}
},
{
"05": {
"magHeading": "33",
"Heading": "36",
"ILS": ""
}
}
],
"length": "1352"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "4"
},
"Caleta Tortel Airport": {
"runways": [
{
"headings": [
{
"04": {
"magHeading": "235",
"Heading": "223",
"ILS": ""
}
},
{
"22": {
"magHeading": "55",
"Heading": "43",
"ILS": ""
}
}
],
"length": "1770"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "3"
},
"Comandante Luis Piedrabuena": {
"runways": [
{
"headings": [
{
"26": {
"magHeading": "272",
"Heading": "260",
"ILS": ""
}
},
{
"08": {
"magHeading": "92",
"Heading": "80",
"ILS": ""
}
}
],
"length": "3505"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "69"
},
"Hipico Flying Club": {
"runways": [
{
"headings": [
{
"02": {
"magHeading": "1",
"Heading": "11",
"ILS": ""
}
},
{
"19": {
"magHeading": "180",
"Heading": "191",
"ILS": ""
}
}
],
"length": "2304"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "934"
},
"Cullen Airport": {
"runways": [],
"TACAN": "",
"ICAO": "",
"elevation": "0"
},
"Aeropuerto de Gobernador Gregores": {
"runways": [
{
"headings": [
{
"28": {
"magHeading": "263",
"Heading": "274",
"ILS": ""
}
},
{
"10": {
"magHeading": "84",
"Heading": "94",
"ILS": ""
}
}
],
"length": "8672"
}
],
"TACAN": "",
"ICAO": "SAWR",
"elevation": "1168"
},
"San Julian": {
"runways": [
{
"headings": [
{
"25": {
"magHeading": "242",
"Heading": "251",
"ILS": ""
}
},
{
"07": {
"magHeading": "62",
"Heading": "71",
"ILS": ""
}
}
],
"length": "6218"
}
],
"TACAN": "",
"ICAO": "SAWJ",
"elevation": "144"
},
"Puerto Williams": {
"runways": [
{
"headings": [
{
"08": {
"magHeading": "70",
"Heading": "82",
"ILS": ""
}
},
{
"26": {
"magHeading": "250",
"Heading": "262",
"ILS": ""
}
}
],
"length": "4312"
}
],
"TACAN": "",
"ICAO": "SCGZ",
"elevation": "83"
},
"Ushuaia": {
"runways": [
{
"headings": [
{
"07": {
"magHeading": "65",
"Heading": "77",
"ILS": ""
}
},
{
"25": {
"magHeading": "245",
"Heading": "257",
"ILS": "111.30"
}
}
],
"length": "7631"
}
],
"TACAN": "",
"ICAO": "SAWH",
"elevation": "60"
},
"Rio Gallegos": {
"runways": [
{
"headings": [
{
"25": {
"magHeading": "245",
"Heading": "256",
"ILS": "110.30"
}
},
{
"07": {
"magHeading": "65",
"Heading": "76",
"ILS": ""
}
}
],
"length": "10585"
}
],
"TACAN": "",
"ICAO": "SAWG",
"elevation": "50"
},
"Rio Grande": {
"runways": [
{
"headings": [
{
"08": {
"magHeading": "68",
"Heading": "79",
"ILS": ""
}
},
{
"26": {
"magHeading": "247",
"Heading": "259",
"ILS": "109.50"
}
}
],
"length": "5922"
}
],
"TACAN": "31X",
"ICAO": "SAWE",
"elevation": "60"
},
"Port Stanley": {
"runways": [
{
"headings": [
{
"27": {
"magHeading": "266",
"Heading": "269",
"ILS": ""
}
},
{
"09": {
"magHeading": "101",
"Heading": "86",
"ILS": ""
}
}
],
"length": "4388"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "67"
},
"El Calafate": {
"runways": [
{
"headings": [
{
"25": {
"magHeading": "241",
"Heading": "253",
"ILS": "108.90"
}
},
{
"07": {
"magHeading": "61",
"Heading": "73",
"ILS": ""
}
}
],
"length": "7579"
}
],
"TACAN": "",
"ICAO": "SAWC",
"elevation": "654"
}
}
}

View File

@ -0,0 +1,466 @@
{
"airfields": {
"Anapa-Vityazevo": {
"ICAO": "URKA",
"elevation": "141",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "214",
"ILS": ""
},
"04": {
"magHeading": "034",
"ILS": ""
}
}
],
"length": "9000"
}
]
},
"Batumi": {
"ICAO": "UGSB",
"elevation": "33",
"TACAN": "16X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "119",
"ILS": ""
},
"31": {
"magHeading": "299",
"ILS": ""
}
}
],
"length": "7500"
}
]
},
"Beslan": {
"ICAO": "URMO",
"elevation": "1722",
"TACAN": "",
"runways": [
{
"headings": [
{
"10": {
"magHeading": "086",
"ILS": "110.50"
},
"28": {
"magHeading": "266",
"ILS": ""
}
}
],
"length": "9600"
}
]
},
"Gelendzhik": {
"ICAO": "URKG",
"elevation": "72",
"TACAN": "",
"runways": [
{
"headings": [
{
"19": {
"magHeading": "212",
"ILS": ""
},
"01": {
"magHeading": "032",
"ILS": ""
}
}
],
"length": "5400"
}
]
},
"Gudauta": {
"ICAO": "UG23",
"elevation": "69",
"TACAN": "",
"runways": [
{
"headings": [
{
"15": {
"magHeading": "144",
"ILS": ""
},
"33": {
"magHeading": "324",
"ILS": ""
}
}
],
"length": "7700"
}
]
},
"Kobuleti": {
"ICAO": "UG5X",
"elevation": "69",
"TACAN": "67X",
"runways": [
{
"headings": [
{
"25": {
"magHeading": "243",
"ILS": ""
},
"07": {
"magHeading": "063",
"ILS": "111.50"
}
}
],
"length": "7400"
}
]
},
"Krasnodar-Center": {
"ICAO": "URKL",
"elevation": "98",
"TACAN": "",
"runways": [
{
"headings": [
{
"27": {
"magHeading": "259",
"ILS": ""
},
"09": {
"magHeading": "079",
"ILS": ""
}
}
],
"length": "7700"
}
]
},
"Krasnodar-Pashkovsky": {
"ICAO": "URKK",
"elevation": "112",
"TACAN": "",
"runways": [
{
"headings": [
{
"23": {
"magHeading": "219",
"ILS": ""
},
"05": {
"magHeading": "039",
"ILS": ""
}
}
],
"length": "9600"
}
]
},
"Krymsk": {
"ICAO": "URKW",
"elevation": "66",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "212",
"ILS": ""
},
"04": {
"magHeading": "032",
"ILS": ""
}
}
],
"length": "8000"
}
]
},
"Kutaisi": {
"ICAO": "UGKO",
"elevation": "148",
"TACAN": "44X",
"runways": [
{
"headings": [
{
"25": {
"magHeading": "247",
"ILS": ""
},
"07": {
"magHeading": "067'",
"ILS": "109.75"
}
}
],
"length": "7700"
}
]
},
"Maykop-Khanskaya": {
"ICAO": "URKH",
"elevation": "591",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "211",
"ILS": ""
},
"04": {
"magHeading": "031",
"ILS": ""
}
}
],
"length": "10100"
}
]
},
"Mineralnye Vody": {
"ICAO": "URMM",
"elevation": "1050",
"TACAN": "",
"runways": [
{
"headings": [
{
"12": {
"magHeading": "108",
"ILS": "111.70"
},
"30": {
"magHeading": "288",
"ILS": "109.30"
}
}
],
"length": "12700"
}
]
},
"Mozdok": {
"ICAO": "XRMF",
"elevation": "507",
"TACAN": "",
"runways": [
{
"headings": [
{
"26": {
"magHeading": "255",
"ILS": ""
},
"08": {
"magHeading": "075",
"ILS": ""
}
}
],
"length": "9400"
}
]
},
"Nalchik": {
"ICAO": "URMN",
"elevation": "1411",
"TACAN": "",
"runways": [
{
"headings": [
{
"24": {
"magHeading": "228",
"ILS": "110.50"
},
"06": {
"magHeading": "048'",
"ILS": ""
}
}
],
"length": "7000"
}
]
},
"Novorossiysk": {
"ICAO": "URKN",
"elevation": "131",
"TACAN": "",
"runways": [
{
"headings": [
{
"22": {
"magHeading": "214",
"ILS": ""
},
"04": {
"magHeading": "034",
"ILS": ""
}
}
],
"length": "5400"
}
]
},
"Senaki-Kolkhi": {
"ICAO": "UGKS",
"elevation": "43",
"TACAN": "31X",
"runways": [
{
"headings": [
{
"27": {
"magHeading": "268",
"ILS": ""
},
"09": {
"magHeading": "088'",
"ILS": "108.90"
}
}
],
"length": "7400"
}
]
},
"Sochi-Adler": {
"ICAO": "URSS",
"elevation": "98",
"TACAN": "",
"runways": [
{
"headings": [
{
"27": {
"magHeading": "235",
"ILS": ""
},
"06": {
"magHeading": "055",
"ILS": "111.10"
}
}
],
"length": "9700"
}
]
},
"Tbilisi-Lochini": {
"ICAO": "UGTB",
"elevation": "1574",
"TACAN": "25X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "121",
"ILS": "110.30"
},
"31": {
"magHeading": "301",
"ILS": "108.90"
}
}
],
"length": "9300"
}
]
},
"Soganlug": {
"ICAO": "UG24",
"elevation": "1500",
"TACAN": "25X",
"runways": [
{
"headings": [
{
"14": {
"magHeading": "125",
"ILS": ""
},
"32": {
"magHeading": "305",
"ILS": ""
}
}
],
"length": "6500"
}
]
},
"Sukhumi-Babushara": {
"ICAO": "UGSS",
"elevation": "43",
"TACAN": "",
"runways": [
{
"headings": [
{
"12": {
"magHeading": "109",
"ILS": ""
},
"30": {
"magHeading": "289",
"ILS": ""
}
}
],
"length": "11400"
}
]
},
"Vaziani": {
"ICAO": "UG27",
"elevation": "1524",
"TACAN": "22X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "129",
"ILS": "108.75"
},
"31": {
"magHeading": "309",
"ILS": "108.75"
}
}
],
"length": "7700"
}
]
}
}
}

View File

@ -0,0 +1,558 @@
{
"airfields": {
"Beatty": {
"ICAO": "KBTY",
"elevation": "3173",
"TACAN": "94X",
"runways": [
{
"headings": [
{
"16": {
"magHeading": "168",
"ILS": ""
},
"34": {
"magHeading": "348",
"ILS": ""
}
}
],
"length": "5500"
}
]
},
"Boulder City": {
"ICAO": "KBVU",
"elevation": "2205",
"TACAN": "114X",
"runways": [
{
"headings": [
{
"15": {
"magHeading": "153",
"ILS": ""
},
"33": {
"magHeading": "333",
"ILS": ""
}
}
],
"length": "3700"
},
{
"headings": [
{
"27": {
"magHeading": "267",
"ILS": ""
},
"09": {
"magHeading": "087",
"ILS": ""
}
}
],
"length": "4400"
}
]
},
"Creech": {
"ICAO": "KINS",
"elevation": "3126",
"TACAN": "87X",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "134",
"ILS": ""
},
"31": {
"magHeading": "314",
"ILS": ""
}
}
],
"length": "4700"
},
{
"headings": [
{
"26": {
"magHeading": "260",
"ILS": ""
},
"08": {
"magHeading": "080",
"ILS": "108.70"
}
}
],
"length": "8700"
}
]
},
"Echo Bay": {
"ICAO": "0L9",
"elevation": "1549",
"TACAN": "",
"runways": [
{
"headings": [
{
"24": {
"magHeading": "246",
"ILS": ""
},
"06": {
"magHeading": "066",
"ILS": ""
}
}
],
"length": "3300"
}
]
},
"Groom Lake": {
"ICAO": "KXTA",
"elevation": "4495",
"TACAN": "18X",
"runways": [
{
"headings": [
{
"14L": {
"magHeading": "145",
"ILS": ""
},
"32R": {
"magHeading": "325",
"ILS": "109.30"
}
}
],
"length": "11700"
},
{
"headings": [
{
"14R (CLOSED)": {
"magHeading": "145",
"ILS": ""
},
"32L (CLOSED)": {
"magHeading": "325",
"ILS": ""
}
}
],
"length": "17800"
}
]
},
"Henderson Executive": {
"ICAO": "KHND",
"elevation": "2493",
"TACAN": "",
"runways": [
{
"headings": [
{
"17L": {
"magHeading": "168",
"ILS": ""
},
"35R": {
"magHeading": "348",
"ILS": ""
}
}
],
"length": "4600"
},
{
"headings": [
{
"17R": {
"magHeading": "168",
"ILS": ""
},
"35L": {
"magHeading": "348",
"ILS": ""
}
}
],
"length": "6100"
}
]
},
"Jean": {
"ICAO": "",
"elevation": "2825",
"TACAN": "",
"runways": [
{
"headings": [
{
"02L": {
"magHeading": "020",
"ILS": ""
},
"20R": {
"magHeading": "200",
"ILS": ""
}
}
],
"length": "4500"
},
{
"headings": [
{
"02R": {
"magHeading": "020",
"ILS": ""
},
"20L": {
"magHeading": "200",
"ILS": ""
}
}
],
"length": "3600"
}
]
},
"McCarran International": {
"ICAO": "KLAS",
"elevation": "2178",
"TACAN": "116X",
"runways": [
{
"headings": [
{
"01L": {
"magHeading": "013",
"ILS": ""
},
"19R": {
"magHeading": "193",
"ILS": ""
}
}
],
"length": "8000"
},
{
"headings": [
{
"01R": {
"magHeading": "013",
"ILS": ""
},
"19L": {
"magHeading": "193",
"ILS": ""
}
}
],
"length": "8000"
},
{
"headings": [
{
"07L": {
"magHeading": "078",
"ILS": ""
},
"25R": {
"magHeading": "258",
"ILS": "110.30"
}
}
],
"length": "10600"
},
{
"headings": [
{
"07R": {
"magHeading": "078",
"ILS": ""
},
"25L": {
"magHeading": "258",
"ILS": ""
}
}
],
"length": "10100"
}
]
},
"Laughlin": {
"ICAO": "KIFP",
"elevation": "673",
"TACAN": "",
"runways": [
{
"headings": [
{
"16": {
"magHeading": "164",
"ILS": ""
},
"34": {
"magHeading": "344",
"ILS": ""
}
}
],
"length": "7100"
}
]
},
"Lincoln County": {
"ICAO": "",
"elevation": "4816",
"TACAN": "",
"runways": [
{
"headings": [
{
"17": {
"magHeading": "170",
"ILS": ""
},
"35": {
"magHeading": "350",
"ILS": ""
}
}
],
"length": "4500"
}
]
},
"Mesquite": {
"ICAO": "67L",
"elevation": "1859",
"TACAN": "",
"runways": [
{
"headings": [
{
"19": {
"magHeading": "197",
"ILS": ""
},
"01": {
"magHeading": "017",
"ILS": ""
}
}
],
"length": "5000"
}
]
},
"Mina": {
"ICAO": "",
"elevation": "4560",
"TACAN": "",
"runways": [
{
"headings": [
{
"13": {
"magHeading": "140",
"ILS": ""
},
"31": {
"magHeading": "320",
"ILS": ""
}
}
],
"length": "4100"
}
]
},
"Nellis": {
"ICAO": "KLSV",
"elevation": "1849",
"TACAN": "12X",
"runways": [
{
"headings": [
{
"03L": {
"magHeading": "029",
"ILS": ""
},
"21R": {
"magHeading": "209",
"ILS": ""
}
}
],
"length": "9800"
},
{
"headings": [
{
"03R": {
"magHeading": "029",
"ILS": ""
},
"21L": {
"magHeading": "209",
"ILS": "109.10"
}
}
],
"length": "9800"
}
]
},
"North Las Vegas": {
"ICAO": "KVGT",
"elevation": "2228",
"TACAN": "",
"runways": [
{
"headings": [
{
"25": {
"magHeading": "256",
"ILS": ""
},
"07": {
"magHeading": "076",
"ILS": ""
}
}
],
"length": "4900"
},
{
"headings": [
{
"12L": {
"magHeading": "122",
"ILS": "110.70"
},
"30R": {
"magHeading": "302",
"ILS": "109.10"
}
}
],
"length": "3800"
},
{
"headings": [
{
"12R": {
"magHeading": "122",
"ILS": ""
},
"30L": {
"magHeading": "302",
"ILS": ""
}
}
],
"length": "4600"
}
]
},
"Pahute Mesa": {
"ICAO": "",
"elevation": "5059",
"TACAN": "",
"runways": [
{
"headings": [
{
"18": {
"magHeading": "182",
"ILS": ""
},
"36": {
"magHeading": "002",
"ILS": ""
}
}
],
"length": "5500"
}
]
},
"Tonopah": {
"ICAO": "KTPH",
"elevation": "5390",
"TACAN": "119X",
"runways": [
{
"headings": [
{
"11": {
"magHeading": "113",
"ILS": ""
},
"29": {
"magHeading": "293",
"ILS": ""
}
}
],
"length": "5600"
},
{
"headings": [
{
"15": {
"magHeading": "153",
"ILS": ""
},
"33": {
"magHeading": "333",
"ILS": ""
}
}
],
"length": "6800"
}
]
},
"Tonopah Test Range": {
"ICAO": "KTNX",
"elevation": "5535",
"TACAN": "77X",
"runways": [
{
"headings": [
{
"14": {
"magHeading": "145",
"ILS": "108.30"
},
"32": {
"magHeading": "325",
"ILS": "111.70"
}
}
],
"length": "11700"
}
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,769 @@
{
"airfields": {
"Abu Dhabi Intl": {
"elevation": "92",
"ICAO": "OMAA",
"runways": [
{
"headings": [
{
"13L": {
"ILS": "",
"magHeading": "127"
},
"31R": {
"ILS": "",
"magHeading": "307"
}
}
],
"length": "13100"
},
{
"headings": [
{
"13R": {
"ILS": "",
"magHeading": "127"
},
"31L": {
"ILS": "",
"magHeading": "307"
}
}
],
"length": "13200"
}
],
"TACAN": ""
},
"Abu Musa Island": {
"elevation": "16",
"ICAO": "OIBA",
"runways": [
{
"headings": [
{
"26": {
"ILS": "",
"magHeading": "262"
},
"08": {
"ILS": "",
"magHeading": "082"
}
}
],
"length": "7800"
}
],
"TACAN": ""
},
"Al Ain Intl": {
"elevation": "814",
"ICAO": "OMAL",
"runways": [
{
"headings": [
{
"19": {
"ILS": "",
"magHeading": "186"
},
"01": {
"ILS": "",
"magHeading": "006"
}
}
],
"length": "12800"
}
],
"TACAN": ""
},
"Al Dhafra AFB": {
"elevation": "52",
"ICAO": "OMAM",
"runways": [
{
"headings": [
{
"13L": {
"ILS": "111.10",
"magHeading": "126"
},
"31R": {
"ILS": "109.10",
"magHeading": "306"
}
}
],
"length": "11700"
},
{
"headings": [
{
"13R": {
"ILS": "108.70",
"magHeading": "16"
},
"31L": {
"ILS": "108.70",
"magHeading": "306"
}
}
],
"length": "11700"
}
],
"TACAN": "96X"
},
"Al Maktoum Intl": {
"elevation": "125",
"ICAO": "OMDW",
"runways": [
{
"headings": [
{
"12": {
"ILS": "111.75",
"magHeading": "120"
},
"30": {
"ILS": "109.75",
"magHeading": "300"
}
}
],
"length": "14400"
}
],
"TACAN": ""
},
"Al Minhad AFB": {
"elevation": "190",
"ICAO": "OMDM",
"runways": [
{
"headings": [
{
"27": {
"ILS": "110.75",
"magHeading": "268"
},
"09": {
"ILS": "110.70",
"magHeading": "088"
}
}
],
"length": "12600"
}
],
"TACAN": "99X"
},
"Al-Bateen": {
"elevation": "12",
"ICAO": "OMAD",
"runways": [
{
"headings": [
{
"13": {
"ILS": "",
"magHeading": "127"
},
"31": {
"ILS": "",
"magHeading": "307"
}
}
],
"length": "7000"
}
],
"TACAN": ""
},
"Bandar Abbas Intl": {
"elevation": "29",
"ICAO": "OIKB",
"runways": [
{
"headings": [
{
"03L": {
"ILS": "",
"magHeading": "25"
},
"21R": {
"ILS": "",
"magHeading": "205"
}
}
],
"length": "11000"
},
{
"headings": [
{
"03R": {
"ILS": "",
"magHeading": "25"
},
"21L": {
"ILS": "109.90",
"magHeading": "205"
}
}
],
"length": "11700"
}
],
"TACAN": "78X"
},
"Bandar Lengeh": {
"elevation": "82",
"ICAO": "OIBL",
"runways": [
{
"headings": [
{
"26": {
"ILS": "",
"magHeading": "259"
},
"08": {
"ILS": "",
"magHeading": "079"
}
}
],
"length": "7900"
}
],
"TACAN": ""
},
"Bandar-e-Jask": {
"elevation": "26",
"ICAO": "OIZJ",
"runways": [
{
"headings": [
{
"24": {
"ILS": "",
"magHeading": "239"
},
"06": {
"ILS": "",
"magHeading": "059"
}
}
],
"length": "7300"
}
],
"TACAN": "110X"
},
"Dubai Intl": {
"elevation": "16",
"ICAO": "OMDB",
"runways": [
{
"headings": [
{
"12L": {
"ILS": "110.10",
"magHeading": "120"
},
"30R": {
"ILS": "110.90",
"magHeading": "300"
}
}
],
"length": "11400"
},
{
"headings": [
{
"12R": {
"ILS": "109.50",
"magHeading": "120"
},
"30L": {
"ILS": "111.30",
"magHeading": "300"
}
}
],
"length": "11400"
}
],
"TACAN": ""
},
"Fujairah Intl": {
"elevation": "121",
"ICAO": "OMFJ",
"runways": [
{
"headings": [
{
"11": {
"ILS": "",
"magHeading": "111"
},
"29": {
"ILS": "111.50",
"magHeading": "291"
}
}
],
"length": "9700"
}
],
"TACAN": ""
},
"Havadarya": {
"elevation": "52",
"ICAO": "OIKP",
"runways": [
{
"headings": [
{
"26": {
"ILS": "",
"magHeading": "257"
},
"08": {
"ILS": "108.90",
"magHeading": "077"
}
}
],
"length": "7200"
}
],
"TACAN": "47X"
},
"Jiroft": {
"elevation": "2664",
"ICAO": "OIKJ",
"runways": [
{
"headings": [
{
"13": {
"ILS": "",
"magHeading": "125"
},
"31": {
"ILS": "",
"magHeading": "305"
}
}
],
"length": "9600"
}
],
"TACAN": ""
},
"Kerman": {
"elevation": "5745",
"ICAO": "OIKK",
"runways": [
{
"headings": [
{
"16": {
"ILS": "",
"magHeading": "155"
},
"34": {
"ILS": "",
"magHeading": "335"
}
}
],
"length": "12400"
}
],
"TACAN": "97X"
},
"Khasab": {
"elevation": "102",
"ICAO": "OOKB",
"runways": [
{
"headings": [
{
"19": {
"ILS": "110.30",
"magHeading": "192"
},
"01": {
"ILS": "",
"magHeading": "012"
}
}
],
"length": "8000"
}
],
"TACAN": ""
},
"Kish Intl": {
"elevation": "115",
"ICAO": "OIBK",
"runways": [
{
"headings": [
{
"10": {
"ILS": "",
"magHeading": "094"
},
"28": {
"ILS": "",
"magHeading": "274"
}
}
],
"length": "11700"
},
{
"headings": [
{
"09R": {
"ILS": "",
"magHeading": "094"
},
"27L": {
"ILS": "",
"magHeading": "274"
}
}
],
"length": "11700"
}
],
"TACAN": "112X"
},
"Lar": {
"elevation": "2635",
"ICAO": "OISL",
"runways": [
{
"headings": [
{
"27": {
"ILS": "",
"magHeading": "268"
},
"09": {
"ILS": "",
"magHeading": "088"
}
}
],
"length": "10100"
}
],
"TACAN": ""
},
"Lavan Island": {
"elevation": "75",
"ICAO": "OIBV",
"runways": [
{
"headings": [
{
"11": {
"ILS": "",
"magHeading": "110"
},
"29": {
"ILS": "",
"magHeading": "290"
}
}
],
"length": "8600"
}
],
"TACAN": ""
},
"Liwa AFB": {
"elevation": "400",
"ICAO": "OMLW",
"runways": [
{
"headings": [
{
"13": {
"ILS": "",
"magHeading": "130"
},
"31": {
"ILS": "",
"magHeading": "310"
}
}
],
"length": "11600"
}
],
"TACAN": "121X"
},
"Qeshm Island": {
"elevation": "26",
"ICAO": "OIKQ",
"runways": [
{
"headings": [
{
"23": {
"ILS": "",
"magHeading": "227"
},
"05": {
"ILS": "",
"magHeading": "047"
}
}
],
"length": "13600"
}
],
"TACAN": ""
},
"Quasoura_airport": {
"elevation": "26",
"ICAO": "OIKQ",
"runways": [
{
"headings": [
{
"23": {
"ILS": "",
"magHeading": "227"
},
"05": {
"ILS": "",
"magHeading": "047"
}
}
],
"length": "13600"
}
],
"TACAN": ""
},
"Ras Al Khaimah Intl": {
"elevation": "330",
"ICAO": "OMRK",
"runways": [
{
"headings": [
{
"17": {
"ILS": "",
"magHeading": "163"
},
"35": {
"ILS": "",
"magHeading": "343"
}
}
],
"length": "12000"
}
],
"TACAN": ""
},
"Sas Al Nakheel": {
"elevation": "10",
"ICAO": "OMNK",
"runways": [
{
"headings": [
{
"16": {
"ILS": "",
"magHeading": "160"
},
"34": {
"ILS": "",
"magHeading": "340"
}
}
],
"length": "6000"
}
],
"TACAN": ""
},
"Sharjah Intl": {
"elevation": "26",
"ICAO": "OMSJ",
"runways": [
{
"headings": [
{
"12L": {
"ILS": "108.55",
"magHeading": "121"
},
"30R": {
"ILS": "111.95",
"magHeading": "301"
}
}
],
"length": "10500"
},
{
"headings": [
{
"12R": {
"ILS": "",
"magHeading": "121"
},
"30L": {
"ILS": "",
"magHeading": "301"
}
}
],
"length": "10500"
}
],
"TACAN": ""
},
"Shiraz Intl": {
"elevation": "4879",
"ICAO": "OISS",
"runways": [
{
"headings": [
{
"11L": {
"ILS": "",
"magHeading": "113"
},
"29R": {
"ILS": "",
"magHeading": "293"
}
}
],
"length": "14000"
},
{
"headings": [
{
"11R": {
"ILS": "",
"magHeading": "113"
},
"29L": {
"ILS": "108.50",
"magHeading": "293"
}
}
],
"length": "13800"
}
],
"TACAN": "94X"
},
"Sir Abu Nuayr": {
"elevation": "26",
"ICAO": "OMSN",
"runways": [
{
"headings": [
{
"10": {
"ILS": "",
"magHeading": "097"
},
"28": {
"ILS": "",
"magHeading": "277"
}
}
],
"length": "2300"
}
],
"TACAN": ""
},
"Sirri Island": {
"elevation": "20",
"ICAO": "OIBS",
"runways": [
{
"headings": [
{
"12": {
"ILS": "",
"magHeading": "125"
},
"30": {
"ILS": "",
"magHeading": "305"
}
}
],
"length": "7900"
}
],
"TACAN": ""
},
"Tunb Island AFB": {
"elevation": "43",
"ICAO": "OIGI",
"runways": [
{
"headings": [
{
"21": {
"ILS": "",
"magHeading": "205"
},
"03": {
"ILS": "",
"magHeading": "025"
}
}
],
"length": "6200"
}
],
"TACAN": ""
},
"Tunb Kochak": {
"elevation": "16",
"ICAO": "OITK",
"runways": [
{
"headings": [
{
"26": {
"ILS": "",
"magHeading": "259"
},
"08": {
"ILS": "",
"magHeading": "079"
}
}
],
"length": "2500"
}
],
"TACAN": "89X"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,525 @@
{
"airfields": {
"High Halden": {
"runways": [
{
"headings": [
{
"11": {
"magHeading": "102",
"Heading": "102",
"ILS": ""
}
},
{
"29": {
"magHeading": "282",
"Heading": "282",
"ILS": ""
}
}
],
"length": "3027"
},
{
"headings": [
{
"21": {
"magHeading": "211",
"Heading": "211",
"ILS": ""
}
},
{
"03": {
"magHeading": "31",
"Heading": "31",
"ILS": ""
}
}
],
"length": "3027"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "104"
},
"Manston": {
"runways": [
{
"headings": [
{
"10": {
"magHeading": "102",
"Heading": "102",
"ILS": ""
}
},
{
"28": {
"magHeading": "282",
"Heading": "282",
"ILS": ""
}
}
],
"length": "9114"
},
{
"headings": [
{
"FIELD N": {
"magHeading": "57",
"Heading": "57",
"ILS": ""
}
},
{
"FIELD S": {
"magHeading": "237",
"Heading": "237",
"ILS": ""
}
}
],
"length": "5261"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "160"
},
"Biggin Hill": {
"runways": [
{
"headings": [
{
"23": {
"magHeading": "227",
"Heading": "228",
"ILS": ""
}
},
{
"05": {
"magHeading": "47",
"Heading": "48",
"ILS": ""
}
}
],
"length": "2430"
},
{
"headings": [
{
"28": {
"magHeading": "287",
"Heading": "288",
"ILS": ""
}
},
{
"10": {
"magHeading": "107",
"Heading": "108",
"ILS": ""
}
}
],
"length": "2594"
},
{
"headings": [
{
"21": {
"magHeading": "208",
"Heading": "208",
"ILS": ""
}
},
{
"03": {
"magHeading": "28",
"Heading": "28",
"ILS": ""
}
}
],
"length": "4939"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "552"
},
"Headcorn": {
"runways": [
{
"headings": [
{
"10": {
"magHeading": "92",
"Heading": "93",
"ILS": ""
}
},
{
"28": {
"magHeading": "272",
"Heading": "273",
"ILS": ""
}
}
],
"length": "3680"
},
{
"headings": [
{
"01": {
"magHeading": "12",
"Heading": "12",
"ILS": ""
}
},
{
"19": {
"magHeading": "192",
"Heading": "192",
"ILS": ""
}
}
],
"length": "3680"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "114"
},
"Detling": {
"runways": [
{
"headings": [
{
"FIELD S": {
"magHeading": "227",
"Heading": "227",
"ILS": ""
}
},
{
"FIELD N": {
"magHeading": "47",
"Heading": "47",
"ILS": ""
}
}
],
"length": "3482"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "623"
},
"Eastchurch": {
"runways": [
{
"headings": [
{
"10": {
"magHeading": "97",
"Heading": "97",
"ILS": ""
}
},
{
"28": {
"magHeading": "277",
"Heading": "277",
"ILS": ""
}
}
],
"length": "2983"
},
{
"headings": [
{
"20": {
"magHeading": "203",
"Heading": "203",
"ILS": ""
}
},
{
"02": {
"magHeading": "23",
"Heading": "23",
"ILS": ""
}
}
],
"length": "2983"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "40"
},
"Abbeville Drucat": {
"runways": [
{
"headings": [
{
"20": {
"magHeading": "203",
"Heading": "203",
"ILS": ""
}
},
{
"2": {
"magHeading": "23",
"Heading": "23",
"ILS": ""
}
}
],
"length": "4877"
},
{
"headings": [
{
"27": {
"magHeading": "270",
"Heading": "270",
"ILS": ""
}
},
{
"09": {
"magHeading": "90",
"Heading": "90",
"ILS": ""
}
}
],
"length": "4877"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "183"
},
"Hawkinge": {
"runways": [
{
"headings": [
{
"FIELD S": {
"magHeading": "180",
"Heading": "180",
"ILS": ""
}
},
{
"FIELD N": {
"magHeading": "0",
"Heading": "0",
"ILS": ""
}
}
],
"length": "2336"
},
{
"headings": [
{
"FIELD W ": {
"magHeading": "218",
"Heading": "219",
"ILS": ""
}
},
{
"FIELD E": {
"magHeading": "38",
"Heading": "39",
"ILS": ""
}
}
],
"length": "2336"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "524"
},
"Lympne": {
"runways": [
{
"headings": [
{
"FIELD E": {
"magHeading": "134",
"Heading": "134",
"ILS": ""
}
},
{
"FIELD W": {
"magHeading": "314",
"Heading": "314",
"ILS": ""
}
}
],
"length": "3054"
},
{
"headings": [
{
"FIELD N": {
"magHeading": "19",
"Heading": "19",
"ILS": ""
}
},
{
"FIELD S": {
"magHeading": "199",
"Heading": "199",
"ILS": ""
}
}
],
"length": "2706"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "351"
},
"Merville Calonne": {
"runways": [
{
"headings": [
{
"32": {
"magHeading": "318",
"Heading": "319",
"ILS": ""
}
},
{
"14": {
"magHeading": "138",
"Heading": "139",
"ILS": ""
}
}
],
"length": "3899"
},
{
"headings": [
{
"26": {
"magHeading": "258",
"Heading": "258",
"ILS": ""
}
},
{
"08": {
"magHeading": "78",
"Heading": "78",
"ILS": ""
}
}
],
"length": "3899"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "52"
},
"Dunkirk Mardyck": {
"runways": [
{
"headings": [
{
"08": {
"magHeading": "80",
"Heading": "81",
"ILS": ""
}
},
{
"26": {
"magHeading": "260",
"Heading": "261",
"ILS": ""
}
}
],
"length": "1839"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "16"
},
"Saint Omer Longuenesse": {
"runways": [
{
"headings": [
{
"08": {
"magHeading": "86",
"Heading": "87",
"ILS": ""
}
},
{
"26": {
"magHeading": "266",
"Heading": "267",
"ILS": ""
}
}
],
"length": "2001"
},
{
"headings": [
{
"FIELD S": {
"magHeading": "208",
"Heading": "208",
"ILS": ""
}
},
{
"FIELD N": {
"magHeading": "28",
"Heading": "28",
"ILS": ""
}
}
],
"length": "1762"
}
],
"TACAN": "",
"ICAO": "",
"elevation": "219"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

19
notes.txt Normal file
View File

@ -0,0 +1,19 @@
v1.0.4 ====================
Changes:
1) Added Olympus Manager for unified configuration management;
2) Added proxy middleware to reroute requests to backend from frontend port (netsh command no longer required);
3) Moved installation folder outside of DCS Saved Games folder. Only necessary and unique files are installed in DCS Saved Games folder by Manager;
4) olympus.json configuration file moved to <DCS Saved Games>/Config folder;
5) olympus_log.txt log file moved to <DCS Saved Games>/Logs folder;
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
CRITICAL NOTICE PLEASE READ!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
THIS VERSION INTRODUCES MAJOR CHANGES IN THE INSTALLATION AND MANAGEMENT OF OLYMPUS. PLEASE MAKE SURE TO READ THE UPDATE INSTRUCTIONS BEFORE INSTALLING.
ALSO, PLEASE MAKE SURE TO UNINSTALL ANY PREVIOUSLY INSTALLED VERSION OF OLYMPUS BEFORE PROCEEDING!
v1.0.3 ====================
First public release.

View File

@ -4,9 +4,9 @@
"port": 3001
},
"authentication": {
"gameMasterPassword": "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6",
"blueCommanderPassword": "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6",
"redCommanderPassword": "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6"
"gameMasterPassword": "4b8823ed9e5c2392ab4a791913bb8ce41956ea32e308b760eefb97536746dd33",
"blueCommanderPassword": "b0ea4230c1558c5313165eda1bdb7fced008ca7f2ca6b823fb4d26292f309098",
"redCommanderPassword": "302bcbaf2a3fdcf175b689bf102d6cdf9328f68a13d4096101bba806482bfed9"
},
"client": {
"port": 3000,

View File

@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
PRODUCTVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
FILEVERSION 1,0,3,0
PRODUCTVERSION 1,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -79,12 +79,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DCS Olympus"
VALUE "FileDescription", "DCS Olympus"
VALUE "FileVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "FileVersion", "1.0.3.0"
VALUE "InternalName", "core.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "core.dll"
VALUE "ProductName", "DCS Olympus"
VALUE "ProductVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "ProductVersion", "1.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@ -212,4 +212,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -2,6 +2,7 @@
#include "logger.h"
#include "luatools.h"
#include "dcstools.h"
#include "defines.h"
#include <algorithm>
@ -38,9 +39,9 @@ void registerLuaFunctions(lua_State* L)
log("protectedCall registered successfully");
}
executeLuaScript(L, instancePath + "..\\Scripts\\mist.lua");
executeLuaScript(L, instancePath + "..\\Scripts\\OlympusCommand.lua");
executeLuaScript(L, instancePath + "..\\Scripts\\unitPayloads.lua");
executeLuaScript(L, instancePath + "..\\Scripts\\templates.lua");
executeLuaScript(L, instancePath + "..\\Scripts\\mods.lua");
executeLuaScript(L, instancePath + MIST_SCRIPT);
executeLuaScript(L, instancePath + OLYMPUS_COMMAND_SCRIPT);
executeLuaScript(L, instancePath + UNIT_PAYLOADS_SCRIPT);
executeLuaScript(L, instancePath + TEMPLATES_SCRIPT);
executeLuaScript(L, instancePath + MODS_SCRIPT);
}

View File

@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
PRODUCTVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
FILEVERSION 1,0,3,0
PRODUCTVERSION 1,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -79,12 +79,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DCS Olympus"
VALUE "FileDescription", "DCS Olympus"
VALUE "FileVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "FileVersion", "1.0.3.0"
VALUE "InternalName", "dcstools.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "dcstools.dll"
VALUE "ProductName", "DCS Olympus"
VALUE "ProductVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "ProductVersion", "1.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@ -172,4 +172,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,6 +1,7 @@
#pragma once
#include "framework.h"
void DllExport setLogDirectory(std::string m_dirPath);
void DllExport log(const std::string& sMessage, bool addToJSON = false);
void DllExport log(const std::wstring& sMessage, bool addToJSON = false);
void DllExport getLogsJSON(json::value& json, unsigned long long time);

View File

@ -8,6 +8,7 @@ public:
void log(const string& sMessage, bool addToJSON);
void log(const wstring& sMessage, bool addToJSON);
void toJSON(json::value& json, unsigned long long time);
void setDirectory(string newDirPath);
static Logger* GetLogger();
@ -20,6 +21,7 @@ private:
static Logger* m_pThis;
static ofstream m_Logfile;
static std::map<unsigned long long, std::string> m_logs;
static string m_dirPath;
mutex mutexLock;

View File

@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
PRODUCTVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
FILEVERSION 1,0,3,0
PRODUCTVERSION 1,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -79,12 +79,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DCS Olympus"
VALUE "FileDescription", "DCS Olympus"
VALUE "FileVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "FileVersion", "1.0.3.0"
VALUE "InternalName", "logger.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "logger.dll"
VALUE "ProductName", "DCS Olympus"
VALUE "ProductVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "ProductVersion", "1.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@ -169,4 +169,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -4,6 +4,11 @@
#define LOGGER Logger::GetLogger()
void setLogDirectory(string m_dirPath)
{
LOGGER->setDirectory(m_dirPath);
}
void log(const string& message, bool addToJSON)
{
LOGGER->log(message, addToJSON);

View File

@ -8,25 +8,35 @@ const string Logger::m_sFileName = LOG_NAME;
Logger* Logger::m_pThis = NULL;
ofstream Logger::m_Logfile;
std::map<unsigned long long, std::string> Logger::m_logs;
std::string Logger::m_dirPath;
Logger::Logger()
{
}
Logger* Logger::GetLogger()
{
if (m_pThis == NULL) {
m_pThis = new Logger();
std::filesystem::path dirPath = std::filesystem::temp_directory_path();
m_Logfile.open((dirPath.string() + m_sFileName).c_str(), ios::out);
}
return m_pThis;
}
void Logger::setDirectory(string newDirPath)
{
m_dirPath = newDirPath;
}
void Logger::Open()
{
std::filesystem::path dirPath = std::filesystem::temp_directory_path();
m_Logfile.open((dirPath.string() + m_sFileName).c_str(), ios::out | ios::app);
try {
m_Logfile.open((m_dirPath + m_sFileName).c_str(), ios::out | ios::app);
}
catch (...) {
std::filesystem::path m_dirPath = std::filesystem::temp_directory_path();
m_Logfile.open((m_dirPath.string() + m_sFileName).c_str(), ios::out | ios::app);
}
}
void Logger::Close()

View File

@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
PRODUCTVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
FILEVERSION 1,0,3,0
PRODUCTVERSION 1,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -79,12 +79,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DCS Olympus"
VALUE "FileDescription", "DCS Olympus"
VALUE "FileVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "FileVersion", "1.0.3.0"
VALUE "InternalName", "luatools.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "luatools.dll"
VALUE "ProductName", "DCS Olympus"
VALUE "ProductVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "ProductVersion", "1.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@ -173,4 +173,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
PRODUCTVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
FILEVERSION 1,0,3,0
PRODUCTVERSION 1,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -79,12 +79,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DCS Olympus"
VALUE "FileDescription", "DCS Olympus"
VALUE "FileVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "FileVersion", "1.0.3.0"
VALUE "InternalName", "olympus.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "olympus.dll"
VALUE "ProductName", "DCS Olympus"
VALUE "ProductVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "ProductVersion", "1.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@ -127,4 +127,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -49,6 +49,9 @@ static int onSimulationStart(lua_State* L)
{
LogInfo(L, "Trying to load core.dll from " + modPath);
SetDllDirectoryA(modPath.c_str());
setLogDirectory(modPath);
log("onSimulationStart callback called successfully");
string dllLocation = modPath + "\\core.dll";

View File

@ -1,8 +1,9 @@
#pragma once
#define VERSION "{{OLYMPUS_VERSION_NUMBER}}.{{OLYMPUS_COMMIT_HASH}}"
#define LOG_NAME "Olympus_log.txt"
#define REST_ADDRESS "http://localhost:30000"
#define LOG_NAME "..\\..\\..\\..\\Logs\\Olympus_log.txt"
#define REST_ADDRESS "http://localhost:3001"
#define REST_URI "olympus"
#define UNITS_URI "units"
#define WEAPONS_URI "weapons"
@ -14,8 +15,14 @@
#define FRAMERATE_TIME_INTERVAL 0.05
#define OLYMPUS_JSON_PATH "..\\olympus.json"
#define AIRCRAFT_DATABASE_PATH "..\\client\\public\\databases\\units\\aircraftdatabase.json"
#define HELICOPTER_DATABASE_PATH "..\\client\\public\\databases\\units\\helicopterdatabase.json"
#define GROUNDUNIT_DATABASE_PATH "..\\client\\public\\databases\\units\\groundunitdatabase.json"
#define NAVYUNIT_DATABASE_PATH "..\\client\\public\\databases\\units\\navyunitdatabase.json"
#define OLYMPUS_JSON_PATH "..\\..\\..\\..\\Config\\olympus.json"
#define AIRCRAFT_DATABASE_PATH "..\\databases\\units\\aircraftdatabase.json"
#define HELICOPTER_DATABASE_PATH "..\\databases\\units\\helicopterdatabase.json"
#define GROUNDUNIT_DATABASE_PATH "..\\databases\\units\\groundunitdatabase.json"
#define NAVYUNIT_DATABASE_PATH "..\\databases\\units\\navyunitdatabase.json"
#define MIST_SCRIPT "..\\Scripts\\mist.lua"
#define OLYMPUS_COMMAND_SCRIPT "..\\Scripts\\OlympusCommand.lua"
#define UNIT_PAYLOADS_SCRIPT "..\\Scripts\\unitPayloads.lua"
#define TEMPLATES_SCRIPT "..\\Scripts\\templates.lua"
#define MODS_SCRIPT "..\\Scripts\\mods.lua"

View File

@ -61,8 +61,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
PRODUCTVERSION {{OLYMPUS_VS_VERSION_NUMBER_1}},0
FILEVERSION 1,0,3,0
PRODUCTVERSION 1,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -79,12 +79,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "DCS Olympus"
VALUE "FileDescription", "DCS Olympus"
VALUE "FileVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "FileVersion", "1.0.3.0"
VALUE "InternalName", "utils.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "utils.dll"
VALUE "ProductName", "TODO: <Product name>"
VALUE "ProductVersion", "{{OLYMPUS_VS_VERSION_NUMBER_2}}.0"
VALUE "ProductVersion", "1.0.3.0"
END
END
BLOCK "VarFileInfo"