comments, move functions, scrapbook progress
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
const WTVClientSessionData = require('./WTVClientSessionData.js');
|
||||
|
||||
class WTVAdmin {
|
||||
|
||||
fs = require('fs');
|
||||
@@ -18,6 +20,12 @@ class WTVAdmin {
|
||||
REASON_EXISTS = 4
|
||||
REASON_NONEXIST = 5
|
||||
|
||||
/**
|
||||
* Creates an instance of WTVAdmin.
|
||||
* @param {Object} minisrv_config
|
||||
* @param {WTVClientSessionData} wtvclient
|
||||
* @param {string} service_name
|
||||
*/
|
||||
constructor(minisrv_config, wtvclient, service_name) {
|
||||
this.minisrv_config = minisrv_config;
|
||||
var { WTVShared } = require("./WTVShared.js");
|
||||
@@ -36,6 +44,12 @@ class WTVAdmin {
|
||||
this.service_name = service_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bans a specific SSID.
|
||||
* @param {string} ssid The SSID to ban
|
||||
* @param {string} admin_ssid The SSID of the admin requesting the ban
|
||||
* @returns {number} The result of the ban operation
|
||||
*/
|
||||
banSSID(ssid, admin_ssid = null) {
|
||||
if (ssid == admin_ssid) {
|
||||
return this.REASON_NOSELF;
|
||||
@@ -58,22 +72,27 @@ class WTVAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbans a specific SSID.
|
||||
* @param {string} ssid The SSID to unban
|
||||
* @returns {number} The result of the unban operation
|
||||
*/
|
||||
unbanSSID(ssid) {
|
||||
var config_changed = false;
|
||||
var fake_config = wtvshared.getUserConfig();
|
||||
var fake_config = this.wtvshared.getUserConfig();
|
||||
if (!fake_config.config) fake_config.config = {};
|
||||
if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = [];
|
||||
if (typeof request_headers.query.ssid === 'string') {
|
||||
if (typeof ssid === 'string') {
|
||||
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
|
||||
if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid.toLowerCase()) {
|
||||
if (fake_config.config.ssid_block_list[k].toLowerCase() == ssid.toLowerCase()) {
|
||||
fake_config.config.ssid_block_list.splice(k, 1);
|
||||
config_changed = true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
|
||||
Object.keys(request_headers.query.ssid).forEach(function (j) {
|
||||
if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid[j].toLowerCase()) {
|
||||
Object.keys(ssid).forEach(function (j) {
|
||||
if (fake_config.config.ssid_block_list[k].toLowerCase() == ssid[j].toLowerCase()) {
|
||||
fake_config.config.ssid_block_list.splice(k, 1);
|
||||
config_changed = true
|
||||
}
|
||||
@@ -89,38 +108,11 @@ class WTVAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
ip2long(ip) {
|
||||
var components;
|
||||
|
||||
if (components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||
var iplong = 0;
|
||||
var power = 1;
|
||||
for (var i = 4; i >= 1; i -= 1) {
|
||||
iplong += power * parseInt(components[i]);
|
||||
power *= 256;
|
||||
}
|
||||
return iplong;
|
||||
}
|
||||
else return -1;
|
||||
}
|
||||
|
||||
isInSubnet(ip, subnet) {
|
||||
// If subnet is given as a single IP address (no CIDR notation)
|
||||
if (subnet.indexOf('/') === -1) {
|
||||
return this.ip2long(ip) === this.ip2long(subnet);
|
||||
} else {
|
||||
// Expect subnet in format "base_ip/prefix_length"
|
||||
let parts = subnet.match(/^(.*?)\/(\d{1,2})$/);
|
||||
if (parts && (this.ip2long(parts[1]) >= 0)) {
|
||||
let base_ip = this.ip2long(parts[1]);
|
||||
let prefixLength = parseInt(parts[2]);
|
||||
let freedom = Math.pow(2, 32 - prefixLength);
|
||||
return (this.ip2long(ip) >= base_ip) && (this.ip2long(ip) < base_ip + freedom);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects a connection attempt based on the client's address or SSID.
|
||||
* @param {boolean} reason_is_ssid If true, the rejection is based on SSID, otherwise on IP address
|
||||
* @returns {string} The reason for rejecting the connection
|
||||
*/
|
||||
rejectConnection(reason_is_ssid) {
|
||||
var rejectReason;
|
||||
if (this.pcservices) {
|
||||
@@ -138,6 +130,11 @@ class WTVAdmin {
|
||||
return rejectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided password matches the service's password.
|
||||
* @param {string} password The password to check
|
||||
* @returns {boolean} True if the password matches, false otherwise
|
||||
*/
|
||||
checkPassword(password) {
|
||||
if (this.pcservices) {
|
||||
if (this.minisrv_config.config.pc_admin.password) {
|
||||
@@ -156,6 +153,10 @@ class WTVAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all registered SSIDs.
|
||||
* @returns {Array} An array of arrays, each containing the SSID and its associated account information
|
||||
*/
|
||||
listRegisteredSSIDs() {
|
||||
var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
|
||||
var self = this;
|
||||
@@ -169,6 +170,11 @@ class WTVAdmin {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current client is authorized to access the service.
|
||||
* @param {boolean} justchecking If true, only checks authorization without rejecting the connection
|
||||
* @return {boolean} True if authorized, false otherwise
|
||||
*/
|
||||
isAuthorized(justchecking = false) {
|
||||
var allowed_ssid = false;
|
||||
var allowed_ip = false;
|
||||
@@ -186,7 +192,7 @@ class WTVAdmin {
|
||||
if (ssid == self.wtvclient.ssid) {
|
||||
allowed_ssid = true;
|
||||
Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) {
|
||||
if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) {
|
||||
if (self.wtvshared.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) {
|
||||
if (allowed_ip) return;
|
||||
allowed_ip = true;
|
||||
}
|
||||
@@ -206,7 +212,7 @@ class WTVAdmin {
|
||||
var self = this;
|
||||
Object.keys(this.minisrv_config.config.pc_admin.ip_whitelist).forEach(function (k) {
|
||||
if (allowed_ip) return;
|
||||
allowed_ip = self.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
|
||||
allowed_ip = self.wtvshared.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -219,6 +225,12 @@ class WTVAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the account information for a specific username.
|
||||
* @param {string} username The username to get the account information for
|
||||
* @param {string|null} directory The directory to search for user accounts, defaults to the session store directory
|
||||
* @returns {Object|null} An object containing account information if the username is found, null otherwise
|
||||
*/
|
||||
getAccountInfo(username, directory = null) {
|
||||
var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
|
||||
var account_data = null;
|
||||
@@ -255,6 +267,11 @@ class WTVAdmin {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the account information for a specific SSID.
|
||||
* @param {string} ssid The SSID to get the account information for
|
||||
* @returns {Object|boolean} An object containing account information if the SSID is registered, false otherwise
|
||||
*/
|
||||
getAccountInfoBySSID(ssid) {
|
||||
var account_info = {};
|
||||
var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
|
||||
@@ -278,13 +295,22 @@ class WTVAdmin {
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the account session data for a specific SSID.
|
||||
* @param {string} ssid The SSID to get the account data for
|
||||
* @returns {WTVClientSessionData} The session data object for the account
|
||||
*/
|
||||
getAccountBySSID(ssid) {
|
||||
var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
|
||||
userSession.user_id = 0;
|
||||
return userSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific SSID is banned.
|
||||
* @param {string} ssid The SSID to check
|
||||
* @returns {boolean} True if the SSID is banned, false otherwise
|
||||
*/
|
||||
isBanned(ssid) {
|
||||
var self = this;
|
||||
var isBanned = false;
|
||||
|
||||
@@ -10,7 +10,6 @@ class WTVAuthor {
|
||||
wtvclient = null;
|
||||
pageFileExt = ".page";
|
||||
pagestore_dir = null;
|
||||
scrapbook_dir = null;
|
||||
pageArr = [];
|
||||
blockArr = [];
|
||||
header = null;
|
||||
@@ -68,18 +67,6 @@ class WTVAuthor {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
scrapbookExists() {
|
||||
if (!this.isguest) {
|
||||
if (this.scrapbook_dir === null) {
|
||||
var userstore_dir = this.wtvclient.getUserStoreDirectory();
|
||||
var store_dir = "Scrapbook" + this.path.sep;
|
||||
this.scrapbook_dir = userstore_dir + store_dir;
|
||||
}
|
||||
}
|
||||
return this.fs.existsSync(this.scrapbook_dir);
|
||||
}
|
||||
|
||||
|
||||
createPagestore() {
|
||||
if (this.pagestoreExists() === false) {
|
||||
@@ -91,92 +78,6 @@ class WTVAuthor {
|
||||
return false;
|
||||
}
|
||||
|
||||
createScrapbook() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
try {
|
||||
if (!this.fs.existsSync(this.scrapbook_dir)) this.fs.mkdirSync(this.scrapbook_dir, { recursive: true });
|
||||
return true;
|
||||
} catch { }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
scrapbookDir() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
return this.scrapbook_dir;
|
||||
}
|
||||
|
||||
listScrapbook() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
const files = this.fs.readdirSync(this.scrapbook_dir);
|
||||
const filteredFiles = files.filter(file => !file.endsWith('.meta'));
|
||||
return filteredFiles;
|
||||
}
|
||||
|
||||
getFreeScrapbookID() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var id = 1;
|
||||
var files = this.fs.readdirSync(this.scrapbook_dir);
|
||||
if (files.length == 0) {
|
||||
return id;
|
||||
}
|
||||
files = files.map(file => parseInt(file.substr(0, file.indexOf('.'))));
|
||||
while (files.includes(id)) {
|
||||
id++;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
getScrapbookImage(id) {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var file = this.scrapbook_dir + id;
|
||||
if (this.fs.existsSync(file)) {
|
||||
return this.fs.readFileSync(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getScrapbookImageType(id) {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var file = this.scrapbook_dir + id + ".meta";
|
||||
if (this.fs.existsSync(file)) {
|
||||
var meta = this.fs.readFileSync(file, 'utf8');
|
||||
try {
|
||||
var metaData = JSON.parse(meta);
|
||||
return metaData.contentType;
|
||||
} catch (e) {
|
||||
this.debug("getScrapbookImageType", "Error parsing metadata for image ID", id, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
addToScrapbook(filename, contentType, data) {
|
||||
try {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var fileout = this.scrapbook_dir + filename;
|
||||
var fileout_meta = this.scrapbook_dir + filename + ".meta";
|
||||
this.fs.writeFileSync(fileout, data);
|
||||
this.fs.writeFileSync(fileout_meta, JSON.stringify({
|
||||
"contentType": contentType
|
||||
}));
|
||||
return true;
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
|
||||
createPage(style) {
|
||||
this.pagestoreExists()
|
||||
var pagestorepath = this.pagestore_dir;
|
||||
|
||||
@@ -16,6 +16,7 @@ class WTVClientSessionData {
|
||||
mailstore = null;
|
||||
favstore = null;
|
||||
pagestore = null;
|
||||
scrapbook_dir = null;
|
||||
login_security = null;
|
||||
capabilities = null;
|
||||
session_storage = "";
|
||||
@@ -108,6 +109,12 @@ class WTVClientSessionData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a ticket data value.
|
||||
* @param {string} key The key of the ticket data
|
||||
* @param {*} value The value to set
|
||||
* @returns {boolean} True if the value was set successfully, false otherwise
|
||||
*/
|
||||
setTicketData(key, value) {
|
||||
if (this.data_store.wtvsec_login) this.data_store.wtvsec_login.setTicketData(key, value);
|
||||
else return false;
|
||||
@@ -115,16 +122,24 @@ class WTVClientSessionData {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves ticket data by key.
|
||||
* @param {string} key The key of the ticket data
|
||||
* @return {*} The value associated with the key, or false if not found
|
||||
*/
|
||||
getTicketData(key) {
|
||||
if (this.data_store.wtvsec_login) return this.data_store.wtvsec_login.getTicketData(key);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes ticket data by key.
|
||||
* @param {string} key The key of the ticket data to delete
|
||||
* @return {boolean} True if the data was deleted successfully, false otherwise
|
||||
*/
|
||||
deleteTicketData(key) {
|
||||
if (this.data_store.wtvsec_login) this.data_store.wtvsec_login.deleteTicketData(key);
|
||||
else return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -328,6 +343,136 @@ class WTVClientSessionData {
|
||||
return (result === false) ? false : true;
|
||||
}
|
||||
|
||||
scrapbookExists() {
|
||||
if (!this.isguest) {
|
||||
if (this.scrapbook_dir === null) {
|
||||
var userstore_dir = this.getUserStoreDirectory();
|
||||
var store_dir = "Scrapbook" + this.path.sep;
|
||||
this.scrapbook_dir = userstore_dir + store_dir;
|
||||
}
|
||||
}
|
||||
return this.fs.existsSync(this.scrapbook_dir);
|
||||
}
|
||||
|
||||
createScrapbook() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
try {
|
||||
if (!this.fs.existsSync(this.scrapbook_dir)) this.fs.mkdirSync(this.scrapbook_dir, { recursive: true });
|
||||
return true;
|
||||
} catch { }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
scrapbookDir() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
return this.scrapbook_dir;
|
||||
}
|
||||
|
||||
listScrapbook() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
const files = this.fs.readdirSync(this.scrapbook_dir);
|
||||
const filteredFiles = files.sort(function(a, b) {
|
||||
return a.localeCompare(b, undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'base'
|
||||
});
|
||||
}).filter(file => !file.endsWith('.meta'));
|
||||
return filteredFiles;
|
||||
}
|
||||
|
||||
getFreeScrapbookID() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var id = 1;
|
||||
var files = this.fs.readdirSync(this.scrapbook_dir);
|
||||
if (files.length == 0) {
|
||||
return id;
|
||||
}
|
||||
files = files.map(file => parseInt(file.substr(0, file.indexOf('.'))));
|
||||
while (files.includes(id)) {
|
||||
id++;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
getScrapbookUsage() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var total_size = 0;
|
||||
var files = this.fs.readdirSync(this.scrapbook_dir);
|
||||
files.forEach(file => {
|
||||
if (!file.endsWith('.meta')) {
|
||||
var file_path = this.scrapbook_dir + file;
|
||||
if (this.fs.existsSync(file_path)) {
|
||||
total_size += this.fs.statSync(file_path).size;
|
||||
}
|
||||
}
|
||||
});
|
||||
return total_size;
|
||||
}
|
||||
|
||||
getScrapbookUsagePercent() {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var total_size = this.getScrapbookUsage();
|
||||
var max_size = this.minisrv_config.config.user_accounts.scrapbook_storage * 1024 * 1024; // convert to bytes
|
||||
if (max_size <= 0) return 0; // no storage limit set
|
||||
var usage_percent = (total_size / max_size) * 100;
|
||||
return Math.round(usage_percent, 2);
|
||||
}
|
||||
|
||||
getScrapbookImage(id) {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var file = this.scrapbook_dir + id;
|
||||
if (this.fs.existsSync(file)) {
|
||||
return this.fs.readFileSync(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getScrapbookImageType(id) {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var file = this.scrapbook_dir + id + ".meta";
|
||||
if (this.fs.existsSync(file)) {
|
||||
var meta = this.fs.readFileSync(file, 'utf8');
|
||||
try {
|
||||
var metaData = JSON.parse(meta);
|
||||
return metaData.contentType;
|
||||
} catch (e) {
|
||||
this.debug("getScrapbookImageType", "Error parsing metadata for image ID", id, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
addToScrapbook(filename, contentType, data) {
|
||||
try {
|
||||
if (this.scrapbookExists() === false) {
|
||||
this.createScrapbook();
|
||||
}
|
||||
var fileout = this.scrapbook_dir + filename;
|
||||
var fileout_meta = this.scrapbook_dir + filename + ".meta";
|
||||
this.fs.writeFileSync(fileout, data);
|
||||
this.fs.writeFileSync(fileout_meta, JSON.stringify({
|
||||
"contentType": contentType
|
||||
}));
|
||||
return true;
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a file from the user store
|
||||
* @param {string} path Path relative to the User File Store
|
||||
@@ -373,12 +518,23 @@ class WTVClientSessionData {
|
||||
return Object.keys(this.session_store.cookies).length || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the user cookies
|
||||
*/
|
||||
resetCookies() {
|
||||
this.session_store.cookies = {};
|
||||
// webtv likes to have at least one cookie in the list, set a dummy cookie for zefie's site expiring in 1 year.
|
||||
this.addCookie("wtv.zefie.com", "/", this.wtvshared.getUTCTime(365 * 86400000), "cookie_type=chocolatechip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a cookie to the user's session store
|
||||
* @param {string|object} domain Domain for the cookie, or an object with cookie data
|
||||
* @param {string|null} path Path for the cookie, defaults to null
|
||||
* @param {string|null} expires Expiration date for the cookie, defaults to null
|
||||
* @param {string|null} data Data for the cookie, defaults to null
|
||||
* @return {boolean} True if the cookie was added successfully, false otherwise
|
||||
*/
|
||||
addCookie(domain, path = null, expires = null, data = null) {
|
||||
if (!this.checkCookies()) this.resetCookies();
|
||||
if (!domain) return false;
|
||||
@@ -413,6 +569,12 @@ class WTVClientSessionData {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a cookie from the user's session store
|
||||
* @param {string} domain Domain of the cookie
|
||||
* @param {string} path Path of the cookie
|
||||
* @return {object|false} Cookie data if found, false otherwise
|
||||
*/
|
||||
getCookie(domain, path) {
|
||||
if (!this.checkCookies()) this.resetCookies();
|
||||
var self = this;
|
||||
@@ -431,6 +593,12 @@ class WTVClientSessionData {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a cookie string from the user's session store
|
||||
* @param {string} domain Domain of the cookie
|
||||
* @param {string} path Path of the cookie
|
||||
* @return {string|false} Cookie string if found, false otherwise
|
||||
*/
|
||||
getCookieString(domain, path) {
|
||||
var cookie_data = this.getCookie(domain, path);
|
||||
/*
|
||||
@@ -443,6 +611,12 @@ class WTVClientSessionData {
|
||||
return cookie_data.cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a cookie from the user's session store
|
||||
* @param {string|object} domain Domain of the cookie, or an object with cookie data
|
||||
* @param {string|null} path Path of the cookie, defaults to null
|
||||
* @return {boolean} True if the cookie was deleted successfully, false otherwise
|
||||
*/
|
||||
deleteCookie(domain, path = null) {
|
||||
var result = false;
|
||||
if (!this.checkCookies()) {
|
||||
@@ -472,12 +646,20 @@ class WTVClientSessionData {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any cookies stored in the session
|
||||
* @return {boolean} True if there are cookies, false otherwise
|
||||
*/
|
||||
checkCookies() {
|
||||
if (!this.session_store.cookies) return false;
|
||||
else if (this.session_store.cookies == []) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all cookies in the user's session store
|
||||
* @return {string} String representation of all cookies, each cookie separated by a null character
|
||||
*/
|
||||
listCookies() {
|
||||
if (!this.checkCookies()) this.resetCookies();
|
||||
var outstring = "";
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
class WTVPCAdmin {
|
||||
|
||||
fs = require('fs');
|
||||
path = require('path');
|
||||
minisrv_config = [];
|
||||
wtvr = null;
|
||||
wtvshared = null;
|
||||
socket = null;
|
||||
WTVClientSessionData = require("./WTVClientSessionData.js");
|
||||
service_name = "wtv-admin";
|
||||
|
||||
constructor(minisrv_config, socket, service_name) {
|
||||
this.minisrv_config = minisrv_config;
|
||||
var { WTVShared } = require("./WTVShared.js");
|
||||
var WTVRegister = require("./WTVRegister.js");
|
||||
this.socket = socket;
|
||||
this.wtvr = new WTVRegister(minisrv_config);
|
||||
this.clientAddress = socket.remoteAddress;
|
||||
this.service_name = service_name;
|
||||
}
|
||||
|
||||
|
||||
ip2long(ip) {
|
||||
var components;
|
||||
|
||||
if (components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||
var iplong = 0;
|
||||
var power = 1;
|
||||
for (var i = 4; i >= 1; i -= 1) {
|
||||
iplong += power * parseInt(components[i]);
|
||||
power *= 256;
|
||||
}
|
||||
return iplong;
|
||||
}
|
||||
else return -1;
|
||||
}
|
||||
|
||||
isInSubnet(ip, subnet) {
|
||||
if (subnet.indexOf('/') == -1) {
|
||||
var mask, base_ip, long_ip = this.ip2long(ip);
|
||||
var mask2, base_ip2, long_ip2 = this.ip2long(ip);
|
||||
return (long_ip == long_ip2);
|
||||
} else {
|
||||
var mask, base_ip, long_ip = this.ip2long(ip);
|
||||
if ((mask = subnet.match(/^(.*?)\/(\d{1,2})$/)) && ((base_ip = this.ip2long(mask[1])) >= 0)) {
|
||||
var freedom = Math.pow(2, 32 - parseInt(mask[2]));
|
||||
return (long_ip > base_ip) && (long_ip < base_ip + freedom - 1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
rejectConnection() {
|
||||
var rejectReason;
|
||||
rejectReason = this.clientAddress + " is not in the whitelist.";
|
||||
console.log(" * Request from IP", this.clientAddress, "for PC Services Admin, but that IP is not authorized.");
|
||||
return rejectReason;
|
||||
}
|
||||
|
||||
checkPassword(password) {
|
||||
if (this.minisrv_config.config.pc_admin.password) {
|
||||
return (password == this.minisrv_config.config.pc_admin.password);
|
||||
} else {
|
||||
// no password set
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
isAuthorized(justchecking = false) {
|
||||
var allowed_ip = false;
|
||||
if (this.minisrv_config.config.pc_admin.ip_whitelist) {
|
||||
var self = this;
|
||||
Object.keys(this.minisrv_config.config.pc_admin.ip_whitelist).forEach(function (k) {
|
||||
allowed_ip = self.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
|
||||
});
|
||||
}
|
||||
if (justchecking) {
|
||||
return allowed_ip;
|
||||
} else {
|
||||
return allowed_ip ? true : this.rejectConnection();
|
||||
}
|
||||
}
|
||||
|
||||
listRegisteredSSIDs() {
|
||||
var search_dir = this.minisrv_config.config.SessionStore + this.path.sep + "accounts";
|
||||
var self = this;
|
||||
var out = [];
|
||||
this.fs.readdirSync(search_dir).forEach(file => {
|
||||
if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory()) {
|
||||
var user = self.getAccountInfoBySSID(file);
|
||||
out.push([file,user]);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
getAccountInfo(username, directory = null) {
|
||||
var search_dir = this.minisrv_config.config.SessionStore + this.path.sep + "accounts";
|
||||
var account_data = null;
|
||||
var self = this;
|
||||
if (directory) search_dir = directory;
|
||||
this.fs.readdirSync(search_dir).forEach(file => {
|
||||
if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory() && account_data === null) {
|
||||
account_data = self.getAccountInfo(username, search_dir + self.path.sep + file);
|
||||
}
|
||||
if (account_data !== null) return;
|
||||
if (!file.match(/.*\.json/ig)) return;
|
||||
try {
|
||||
var temp_session_data_file = self.fs.readFileSync(search_dir + self.path.sep + file, 'Utf8');
|
||||
var temp_session_data = JSON.parse(temp_session_data_file);
|
||||
|
||||
if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) {
|
||||
account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.minisrv_config.config.SessionStore + this.path.sep + "accounts", "").split(this.path.sep)[1]];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(" # Error parsing Session Data JSON", search_dir + self.path.sep + file, e);
|
||||
}
|
||||
});
|
||||
if (account_data !== null) {
|
||||
if (account_data.ssid) return account_data;
|
||||
var account_info = {};
|
||||
account_info.ssid = account_data[1];
|
||||
account_info.username = account_data[0].subscriber_username;
|
||||
account_info.user_id = account_data[0].subscriber_userid;
|
||||
var userSession = new this.WTVClientSessionData(this.minisrv_config, account_info.ssid);
|
||||
userSession.user_id = 0;
|
||||
account_info.account_users = userSession.listPrimaryAccountUsers();
|
||||
return account_info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getAccountInfoBySSID(ssid) {
|
||||
var account_info = {};
|
||||
var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
|
||||
userSession.user_id = 0;
|
||||
if (userSession.isRegistered(false)) {
|
||||
account_info.ssid = ssid;
|
||||
account_info.account_users = userSession.listPrimaryAccountUsers();
|
||||
if (account_info.account_users) {
|
||||
if (account_info.account_users['subscriber']) {
|
||||
account_info.username = account_info.account_users['subscriber'].subscriber_username;
|
||||
} else {
|
||||
account_info.username = account_info.account_users[0];
|
||||
}
|
||||
} else {
|
||||
account_info.username = account_info.account_users[0];
|
||||
}
|
||||
|
||||
account_info.user_id = 0;
|
||||
return account_info;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
getAccountBySSID(ssid) {
|
||||
var userSession = new this.WTVClientSessionData(this.minisrv_config, ssid);
|
||||
userSession.user_id = 0;
|
||||
return userSession;
|
||||
}
|
||||
|
||||
isBanned(ssid) {
|
||||
var self = this;
|
||||
var isBanned = false;
|
||||
if (this.minisrv_config.config.ssid_block_list) {
|
||||
Object.keys(this.minisrv_config.config.ssid_block_list).forEach(function (k) {
|
||||
if (self.minisrv_config.config.ssid_block_list[k] == ssid) {
|
||||
isBanned = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return isBanned;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WTVPCAdmin;
|
||||
@@ -21,12 +21,22 @@ class WTVRegister {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the username is valid according to the configured rules.
|
||||
* @param {string} username The username to check
|
||||
* @returns {boolean} True if the username is valid, false otherwise
|
||||
*/
|
||||
checkUsernameSanity(username) {
|
||||
var regex_str = "^([A-Za-z0-9-\_]{" + this.minisrv_config.config.user_accounts.min_username_length + "," + this.minisrv_config.config.user_accounts.max_username_length + "})$";
|
||||
var regex = new RegExp(regex_str);
|
||||
return regex.test(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the SSID is already registered.
|
||||
* @param {string} ssid The SSID to check
|
||||
* @returns {boolean} True if the SSID is available for registration, false if it already has an account registered.
|
||||
*/
|
||||
checkSSIDAvailable(ssid) {
|
||||
var directory = (directory) ? directory : this.session_store_dir + this.path.sep + "accounts";
|
||||
var available = true;
|
||||
@@ -41,6 +51,12 @@ class WTVRegister {
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the username is already taken.
|
||||
* @param {string} username The username to check
|
||||
* @param {string} directory The directory to search for user accounts
|
||||
* @returns {boolean} True if the username is available, false if it is already taken
|
||||
*/
|
||||
checkUsernameAvailable(username, directory = null) {
|
||||
var self = this;
|
||||
var return_val = false;
|
||||
|
||||
@@ -7,7 +7,7 @@ class WTVSSL {
|
||||
|
||||
getCACert() {
|
||||
// return the CA cert
|
||||
const caCertFile = this.wtvshared.getServiceDep("https/ca.der")
|
||||
const caCertFile = this.wtvshared.getServiceDep("https/ca.der", true)
|
||||
if (!this.wtvshared.fs.existsSync(caCertFile)) {
|
||||
throw new Error("CA certificate file not found");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ const CryptoJS = require('crypto-js');
|
||||
const endianness = require('endianness');
|
||||
var RC4 = require('rc4-crypto');
|
||||
var crypto = require('crypto');
|
||||
var WTVShared = require("./WTVShared.js")['WTVShared'];
|
||||
|
||||
/**
|
||||
* Javascript implementation of WTVP Security
|
||||
@@ -34,6 +35,7 @@ class WTVSec {
|
||||
RC4Session = new Array();
|
||||
minisrv_config = [];
|
||||
update_ticket = false;
|
||||
wtvshared = null;
|
||||
ticket_store = {};
|
||||
|
||||
/**
|
||||
@@ -46,6 +48,7 @@ class WTVSec {
|
||||
*/
|
||||
constructor(minisrv_config, wtv_incarnation = 1) {
|
||||
this.minisrv_config = minisrv_config;
|
||||
this.wtvshared = new WTVShared(minisrv_config);
|
||||
this.initial_shared_key = CryptoJS.enc.Base64.parse(this.minisrv_config.config.keys.initial_shared_key);
|
||||
|
||||
if (this.initial_shared_key.sigBytes === 8) {
|
||||
@@ -58,7 +61,6 @@ class WTVSec {
|
||||
|
||||
/**
|
||||
* Set the wtv-incarnation for this instance
|
||||
*
|
||||
* @param {Number} wtv_incarnation
|
||||
*/
|
||||
set_incarnation(wtv_incarnation) {
|
||||
@@ -77,16 +79,16 @@ class WTVSec {
|
||||
|
||||
/**
|
||||
* Clones a WordArray to allow modification without referencing its original
|
||||
* @param {CryptoJS.lib.WordArray} wa
|
||||
*
|
||||
* @param {CryptoJS.lib.WordArray} wordArray
|
||||
* @returns {CryptoJS.lib.WordArray}
|
||||
*/
|
||||
DuplicateWordArray(wa) {
|
||||
return CryptoJS.lib.WordArray.create(this.wordArrayToBuffer(wa));
|
||||
DuplicateWordArray(wordArray) {
|
||||
return CryptoJS.lib.WordArray.create(this.wordArrayToBuffer(wordArray));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the wtv-ticket for this instance
|
||||
* @returns {Base64} wtv-ticket
|
||||
*/
|
||||
PrepareTicket() {
|
||||
// store last challenge response in ticket
|
||||
@@ -110,20 +112,8 @@ class WTVSec {
|
||||
return this.ticket_b64;
|
||||
}
|
||||
|
||||
tryDecodeJSON(json_string) {
|
||||
var out;
|
||||
try {
|
||||
out = JSON.parse(json_string);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
out = {};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a wtv-ticket to set up this instance
|
||||
*
|
||||
* @param {Base64} ticket_b64
|
||||
*/
|
||||
DecodeTicket(ticket_b64) {
|
||||
@@ -146,7 +136,7 @@ class WTVSec {
|
||||
var challenge_code_b64 = CryptoJS.enc.Hex.parse(challenge_code).toString(CryptoJS.enc.Base64);
|
||||
if ((ticket_dec.sigBytes * 2) >= challenge_code.length) {
|
||||
var ticket_data_dec = CryptoJS.enc.Hex.parse(ticket_dec.toString().substring(data_offset)).toString(CryptoJS.enc.Utf8);
|
||||
this.ticket_store = this.tryDecodeJSON(ticket_data_dec);
|
||||
this.ticket_store = this.wtvshared.tryDecodeJSON(ticket_data_dec);
|
||||
} else {
|
||||
this.ticket_store = {};
|
||||
}
|
||||
@@ -155,6 +145,11 @@ class WTVSec {
|
||||
if (this.minisrv_config.config.debug_flags.debug) console.log(" * Decoded session from wtv-ticket with ticket_store:", this.ticket_store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ticket data for this instance
|
||||
* @param {string} key The key of the ticket data to retrieve
|
||||
* @returns {any} The ticket data for the specified key, or null if not found
|
||||
*/
|
||||
getTicketData(key = null) {
|
||||
if (typeof (this.ticket_store) === 'session_store') return null;
|
||||
else if (key === null) return this.ticket_store;
|
||||
@@ -162,6 +157,11 @@ class WTVSec {
|
||||
else return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ticket data for this instance
|
||||
* @param {string} key The key of the ticket data to set
|
||||
* @param {any} value The value to set for the specified key
|
||||
*/
|
||||
setTicketData(key, value) {
|
||||
if (key === null) throw ("WTVSec.setTicketData(): invalid key provided");
|
||||
if (typeof (this.ticket_store) === 'undefined') this.ticket_store = {};
|
||||
@@ -170,6 +170,10 @@ class WTVSec {
|
||||
this.update_ticket = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the ticket data for this instance
|
||||
* @param {string} key The key of the ticket data to delete
|
||||
*/
|
||||
deleteTicketData(key) {
|
||||
if (key === null) throw ("WTVSec.deleteTicketData(): invalid key provided");
|
||||
if (typeof (this.ticket_store) === 'undefined') {
|
||||
@@ -234,7 +238,6 @@ class WTVSec {
|
||||
|
||||
/**
|
||||
* Generates a wtv-challenge for this instance
|
||||
*
|
||||
* @returns {Base64} wtv-challenge
|
||||
*/
|
||||
IssueChallenge() {
|
||||
@@ -246,7 +249,7 @@ class WTVSec {
|
||||
* bytes 64 - 80: session key 2 used in RC4 encryption triggered by SECURE ON
|
||||
* bytes 80 - 88: new key for future challenges
|
||||
* bytes 88 - 104: MD5 of 8 - 88
|
||||
* bytes 104 - 112: padding.not important
|
||||
* bytes 104 - 112: padding. seemingly not important, but by default is 8 bytes of 0x08
|
||||
*/
|
||||
const challenge_id = CryptoJS.lib.WordArray.random(8);
|
||||
const echo_me = CryptoJS.lib.WordArray.random(40);
|
||||
@@ -280,8 +283,7 @@ class WTVSec {
|
||||
/**
|
||||
* convert a CryptoJS.lib.WordArray to a Javascript Buffer
|
||||
* @param {CryptoJS.lib.WordArray} wordArray
|
||||
*
|
||||
* #returns {Buffer} JS Buffer object
|
||||
* @returns {Buffer} JS Buffer object
|
||||
*/
|
||||
wordArrayToBuffer(wordArray) {
|
||||
if (wordArray) return new Buffer.from(wordArray.toString(CryptoJS.enc.Hex), 'hex');
|
||||
@@ -291,7 +293,6 @@ class WTVSec {
|
||||
/**
|
||||
* Starts an encryption session
|
||||
* @param {Number} rc4session Session Type (0 = enc k1, 1 = dec k1, 2 = enc k2, 3 = dec k2, default: all)
|
||||
*
|
||||
*/
|
||||
SecureOn(rc4session = null) {
|
||||
if (this.minisrv_config.config.debug_flags.debug) console.log(" # Generating RC4 sessions with wtv-incarnation: " + this.incarnation);
|
||||
@@ -326,7 +327,6 @@ class WTVSec {
|
||||
* RC4 Encrypt data
|
||||
* @param {Number} keynum Which key to use (0 = k1, 1 = k2)
|
||||
* @param {CryptoJS.lib.WordArray|ArrayBuffer|Buffer} data Data to encrypt
|
||||
*
|
||||
* @returns {ArrayBuffer} Encrypted data
|
||||
*/
|
||||
Encrypt(keynum, data) {
|
||||
@@ -357,8 +357,8 @@ class WTVSec {
|
||||
* RC4 Decrypt data
|
||||
* @param {Number} keynum Which key to use (0 = k1, 1 = k2)
|
||||
* @param {CryptoJS.lib.WordArray|ArrayBuffer|Buffer} data Data to decrypt
|
||||
*
|
||||
* @returns {ArrayBuffer} Decrypted data
|
||||
* @notice This function is an alias for Encrypt, as WTVSec uses the same method for both encryption and decryption.
|
||||
*/
|
||||
Decrypt(keynum, data) {
|
||||
return this.Encrypt(keynum, data)
|
||||
|
||||
@@ -23,6 +23,12 @@ class WTVShared {
|
||||
|
||||
minisrv_config = [];
|
||||
|
||||
/**
|
||||
* Constructor for WTVShared class
|
||||
* @param {object} minisrv_config The configuration object for the minisrv
|
||||
* @param {boolean} quiet If true, suppresses console output
|
||||
* @notice If minisrv_config is null, it will attempt to read the configuration from the minisrv_config.json file
|
||||
* */
|
||||
constructor(minisrv_config, quiet = false) {
|
||||
if (minisrv_config == null) this.minisrv_config = this.readMiniSrvConfig(true, !quiet);
|
||||
else this.minisrv_config = minisrv_config;
|
||||
@@ -47,7 +53,120 @@ class WTVShared {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an IP address to a hexadecimal string (WTV)
|
||||
* @param {string} ip The IP address to convert
|
||||
* @returns {string} The hexadecimal representation of the IP address
|
||||
* @throws {Error} If the IP address is invalid
|
||||
*/
|
||||
ipToHex(ip) {
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) {
|
||||
throw new Error('Invalid IP address');
|
||||
}
|
||||
let num = 0;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const part = parseInt(parts[i], 10);
|
||||
if (part < 0 || part > 255) {
|
||||
throw new Error('Invalid IP address');
|
||||
}
|
||||
num = (num << 8) | part;
|
||||
}
|
||||
// Convert to unsigned 32-bit number before converting to hex
|
||||
return "0x" + (num >>> 0).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an IP address to a long integer
|
||||
* @param {string} ip The IP address to convert
|
||||
* @returns {number} The long integer representation of the IP address, or -1 if the IP address is invalid
|
||||
*/
|
||||
ip2long(ip) {
|
||||
var components;
|
||||
if (components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||
var iplong = 0;
|
||||
var power = 1;
|
||||
for (var i = 4; i >= 1; i -= 1) {
|
||||
iplong += power * parseInt(components[i]);
|
||||
power *= 256;
|
||||
}
|
||||
return iplong;
|
||||
}
|
||||
else return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an IP address is in a given subnet.
|
||||
* @param {string} ip The IP address to check
|
||||
* @param {string} subnet The subnet in CIDR notation
|
||||
* @returns {boolean} True if the IP address is in the subnet, false otherwise
|
||||
*/
|
||||
isInSubnet(ip, subnet) {
|
||||
if (subnet.indexOf('/') == -1) {
|
||||
var mask, base_ip, long_ip = this.ip2long(ip);
|
||||
var mask2, base_ip2, long_ip2 = this.ip2long(ip);
|
||||
return (long_ip == long_ip2);
|
||||
} else {
|
||||
var mask, base_ip, long_ip = this.ip2long(ip);
|
||||
if ((mask = subnet.match(/^(.*?)\/(\d{1,2})$/)) && ((base_ip = this.ip2long(mask[1])) >= 0)) {
|
||||
var freedom = Math.pow(2, 32 - parseInt(mask[2]));
|
||||
return (long_ip > base_ip) && (long_ip < base_ip + freedom - 1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array to a 32-bit unsigned integer (big-endian)
|
||||
* @param {Uint8Array} bytes The byte array
|
||||
* @param {number} offset The offset within the byte array
|
||||
* @returns {number} The 32-bit unsigned integer
|
||||
*/
|
||||
toUint32(bytes, offset) {
|
||||
return (
|
||||
(bytes[offset] << 24) >>> 0 |
|
||||
(bytes[offset + 1] << 16) |
|
||||
(bytes[offset + 2] << 8) |
|
||||
(bytes[offset + 3])
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a 32-bit unsigned integer to a byte array (big-endian)
|
||||
* @param {number} num The 32-bit unsigned integer
|
||||
* @returns {number[]} The byte array representation
|
||||
* @notice The output is an array of 4 bytes, each byte is an unsigned integer (0-255)
|
||||
*/
|
||||
uint32ToBytes(num) {
|
||||
return [
|
||||
(num >>> 24) & 0xff,
|
||||
(num >>> 16) & 0xff,
|
||||
(num >>> 8) & 0xff,
|
||||
num & 0xff,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to decode a JSON string
|
||||
* @param {String} json_string
|
||||
* @returns {Object} The decoded JSON object, or an empty object if decoding fails.
|
||||
*/
|
||||
tryDecodeJSON(json_string) {
|
||||
var out;
|
||||
try {
|
||||
out = JSON.parse(json_string);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
out = {};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the box name based on the client ROM type
|
||||
* @param {string} client_rom_type The client ROM type
|
||||
* @returns {string} The box name
|
||||
*/
|
||||
getBoxName(client_rom_type) {
|
||||
switch (client_rom_type) {
|
||||
case "bf0app":
|
||||
@@ -94,14 +213,22 @@ class WTVShared {
|
||||
return crc.toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
// check if the SSID has a valid checksum
|
||||
/**
|
||||
* Checks if the SSID has a valid checksum
|
||||
* @param {string} ssid The SSID to check
|
||||
* @return {boolean} true if the SSID is valid, false if not
|
||||
*/
|
||||
checkSSID(ssid) {
|
||||
if (ssid.slice(-2) == this.getSSIDCRC(ssid))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses variables in a string, replacing %ServiceDeps% with the service dependencies
|
||||
* @param {string} s The string to parse
|
||||
* @returns {string} The parsed string
|
||||
*/
|
||||
parseConfigVars(s) {
|
||||
if (s.indexOf("%ServiceDeps%") >= 0)
|
||||
return this.getServiceDep(s.replace("%ServiceDeps%", ""), true);
|
||||
@@ -155,7 +282,6 @@ class WTVShared {
|
||||
return src;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the user has been whitelisted for wtv-admin
|
||||
* @param {object} wtvclient the clientSessionData object for the user
|
||||
@@ -169,6 +295,11 @@ class WTVShared {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JSON string, removing unsupported comments
|
||||
* @param {string} json JSON string to parse
|
||||
* @returns {object} Parsed JSON object
|
||||
*/
|
||||
parseJSON(json) {
|
||||
if (typeof json !== 'string') json = json ? json.toString() : '';
|
||||
let result = '';
|
||||
@@ -206,7 +337,6 @@ class WTVShared {
|
||||
return JSON.parse(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to convert val into a boolean
|
||||
* @param {string,int,boolean} val
|
||||
@@ -258,7 +388,6 @@ class WTVShared {
|
||||
return entitized;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to sanitize HTML code to remove possible exploits when embedded in a WebTV Service
|
||||
* @param {string} string The string to sanitize
|
||||
@@ -332,7 +461,7 @@ class WTVShared {
|
||||
* @param {string} headers Header string to convert
|
||||
* @param {boolean} response If true, the headers are a response, otherwise they are a request
|
||||
* @return {object} Headers object
|
||||
* */
|
||||
*/
|
||||
headerStringToObj(headers, response = false) {
|
||||
var inc_headers = 0;
|
||||
var headers_obj = {};
|
||||
@@ -421,7 +550,6 @@ class WTVShared {
|
||||
return typeof str === 'string' && /^[\x00-\x7F]*$/.test(str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to determine if the string contains HTML
|
||||
* @param {string} str
|
||||
@@ -432,7 +560,6 @@ class WTVShared {
|
||||
return typeof str === 'string' && pattern.test(str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to determine if the string is Base64 or not
|
||||
* @param {string} str String to check
|
||||
@@ -455,7 +582,11 @@ class WTVShared {
|
||||
return new RegExp(regex, 'gi').test(str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a UTF-8 string to a regular string
|
||||
* @param {string} utf8String The UTF-8 encoded string to decode
|
||||
* @returns {string} The decoded string
|
||||
* */
|
||||
utf8Decode(utf8String) {
|
||||
if (typeof utf8String !== 'string') {
|
||||
throw new TypeError("parameter 'utf8String' is not a string");
|
||||
@@ -465,7 +596,11 @@ class WTVShared {
|
||||
return textDecoder.decode(bytes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a buffer containing ISO-8859-1 encoded text to a UTF-8 string
|
||||
* @param {Buffer} buf The buffer to decode
|
||||
* @returns {string} The decoded string
|
||||
*/
|
||||
decodeBufferText(buf) {
|
||||
var out = "";
|
||||
out = this.utf8Decode(this.iconv.decode(Buffer.from(buf),'ISO-8859-1'));
|
||||
@@ -487,7 +622,6 @@ class WTVShared {
|
||||
return this.fixPathSlashes(check_path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detects if the client is in MiniBrowser mode
|
||||
* @param {object} ssid_session
|
||||
@@ -507,6 +641,12 @@ class WTVShared {
|
||||
return (this.isMiniBrowser(ssid_session) || parseInt(ssid_session.get("wtv-system-version")) < minBuild) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user configuration from the user_config.json file
|
||||
* @returns {object} User configuration object
|
||||
* @notice If the file does not exist, it will return an empty object
|
||||
* @notice If the file exists but cannot be parsed, it will terminate the process with an error message
|
||||
*/
|
||||
getUserConfig() {
|
||||
try {
|
||||
var user_config_filename = this.getAbsolutePath("user_config.json", this.appdir);
|
||||
@@ -577,7 +717,6 @@ class WTVShared {
|
||||
return ssid_obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Alias for parseSSID, but just the manufacture info
|
||||
* @param {string} ssid
|
||||
@@ -589,6 +728,13 @@ class WTVShared {
|
||||
else return this.parseSSID(ssid).manufacturer || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the MiniSrv configuration files
|
||||
* @param {boolean} user_config If true, also read user_config.json
|
||||
* @param {boolean} notices If true, show notices
|
||||
* @param {boolean} reload_notice If true, show reload notice
|
||||
* @returns {object} The MiniSrv configuration object
|
||||
*/
|
||||
readMiniSrvConfig(user_config = true, notices = true, reload_notice = false) {
|
||||
const log = (msg) => {
|
||||
if (notices || reload_notice) console.log(msg);
|
||||
@@ -642,8 +788,15 @@ class WTVShared {
|
||||
log(" *** Configuration successfully read.");
|
||||
this.minisrv_config = minisrv_config;
|
||||
return this.minisrv_config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrates the user configuration into the main configuration object
|
||||
* @param {object} main The main configuration object
|
||||
* @param {object} user The user configuration object
|
||||
* @returns {object} The integrated configuration object
|
||||
* @notice This will overwrite any existing keys in the main configuration with the user configuration
|
||||
* */
|
||||
integrateConfig(main, user) {
|
||||
for (const key in user) {
|
||||
if (typeof user[key] === 'object' && user[key] !== null && !Array.isArray(user[key])) {
|
||||
@@ -655,7 +808,11 @@ class WTVShared {
|
||||
return main;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the user configuration to the user_config.json file
|
||||
* @param {object} config Configuration object to write
|
||||
* @returns {boolean} true if successful, false if not
|
||||
*/
|
||||
writeToUserConfig(config) {
|
||||
if (config) {
|
||||
try {
|
||||
@@ -684,7 +841,6 @@ class WTVShared {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a random string
|
||||
* @param {int} len desired generated string length
|
||||
@@ -718,7 +874,6 @@ class WTVShared {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Any alias of generateString with optional special characters enabled as well
|
||||
* @param {string} len desired generated string length
|
||||
@@ -737,6 +892,13 @@ class WTVShared {
|
||||
return this.minisrv_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a string to a specified length, breaking at whitespace
|
||||
* @param {string} string The string to wrap
|
||||
* @param {number} len The maximum line length
|
||||
* @param {string} join The string to join the wrapped lines with (default is "\n")
|
||||
* @returns {string} The wrapped string
|
||||
*/
|
||||
lineWrap(string, len = 72, join = "\n") {
|
||||
if (string.length <= len) return string;
|
||||
|
||||
@@ -771,6 +933,11 @@ class WTVShared {
|
||||
return this.getFileLastModified(file).toUTCString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Last-Modified date in a Date object with UTC time
|
||||
* @param {string} file Path to a file
|
||||
* @return {Date} Date object with UTC time
|
||||
*/
|
||||
getFileLastModifiedUTCObj(file) {
|
||||
return new Date(new Date().setUTCSeconds(this.getFileLastModified(file).getUTCSeconds()));
|
||||
}
|
||||
@@ -842,6 +1009,11 @@ class WTVShared {
|
||||
return decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Censors the SSID by replacing parts of it with asterisks
|
||||
* @param {string} ssid The SSID to censor
|
||||
* @returns {string} Censored SSID
|
||||
* */
|
||||
censorSSID(ssid) {
|
||||
if (ssid) {
|
||||
if (ssid.slice(0, 8) === "MSTVSIMU") {
|
||||
@@ -875,7 +1047,11 @@ class WTVShared {
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filters sensitive information from request logs
|
||||
* @param {object} obj The request log object to filter
|
||||
* @returns {object} Filtered request log object
|
||||
* */
|
||||
filterRequestLog(obj) {
|
||||
const passwordRegex = /(^pass$|passw(or)?d)/i;
|
||||
var newobj = this.cloneObj(obj); // Clone the object once at the beginning
|
||||
@@ -891,8 +1067,11 @@ class WTVShared {
|
||||
return newobj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Decodes post data from a request log object
|
||||
* @param {object} obj The request log object
|
||||
* @returns {object} The request log object with decoded post data
|
||||
*/
|
||||
decodePostData(obj) {
|
||||
if (obj.post_data) {
|
||||
const filterPasswords = this.minisrv_config.config.filter_passwords_in_logs === true;
|
||||
@@ -924,11 +1103,12 @@ class WTVShared {
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
// DON'T USE THIS
|
||||
// Saved for reference until I come up with a better way
|
||||
// If used, this will exceed the stack limit over time
|
||||
unloadModule(moduleName) {
|
||||
// Prevent usage
|
||||
return;
|
||||
// Search for the module in the require cache
|
||||
let resolvedPath = require.resolve(moduleName);
|
||||
|
||||
@@ -973,8 +1153,6 @@ class WTVShared {
|
||||
return this.path.resolve(path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a percentage
|
||||
* @param {number} partialValue
|
||||
@@ -1005,6 +1183,12 @@ class WTVShared {
|
||||
return path.reverse().split(".")[0].reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a line from a file by line number
|
||||
* @param {string} filename The file to read
|
||||
* @param {number} lineNo The line number to read (0-indexed)
|
||||
* @param {function} callback Callback function to call with the line or an error
|
||||
* */
|
||||
getLineFromFile(filename, lineNo, callback) {
|
||||
let lineCount = 0;
|
||||
const lineReader = this.readline.createInterface({
|
||||
@@ -1034,8 +1218,6 @@ class WTVShared {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks if service is enabled or disabled in the config
|
||||
* @param {string} service Service Name
|
||||
@@ -1127,7 +1309,6 @@ class WTVShared {
|
||||
return [headers, message];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strips bad things from paths
|
||||
* @param {string} base Base path
|
||||
@@ -1169,7 +1350,6 @@ class WTVShared {
|
||||
return pathModule.normalize(normalizedPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes sure an SSID is clean, and doesn't contain any exploitable characters
|
||||
* @param {string} ssid
|
||||
@@ -1181,12 +1361,22 @@ class WTVShared {
|
||||
return ssid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure a string path is clean, and doesn't contain any exploitable characters
|
||||
* @param {string} path
|
||||
* @returns {string|null} Sanitized Path
|
||||
* */
|
||||
makeSafeStringPath(path = "") {
|
||||
path = path.replace(/[^\w]/g, "").replace(/\.\./g, "");
|
||||
if (path.length == 0) path = null;
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a base64 compressed string
|
||||
* @param {string|Buffer} data Base64 encoded compressed data
|
||||
* @returns {string} Uncompressed string
|
||||
* */
|
||||
unpackCompressedB64(data) {
|
||||
var data_buf = (typeof data === 'object') ? Buffer.from(data.toString('ascii'), 'base64') : Buffer.from(data, 'base64');
|
||||
return this.zlib.inflateSync(data_buf, { finishFlush: this.zlib.Z_SYNC_FLUSH }).toString('ascii');
|
||||
@@ -1252,6 +1442,7 @@ class WTVShared {
|
||||
}
|
||||
return keys.indexOf(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an object to the desired location in the object (reorder)
|
||||
* @param {string|int} currentKey Name of the object Key to move or the index to move
|
||||
@@ -1322,7 +1513,7 @@ class clientShowAlert {
|
||||
* @param {string} buttonlabel2 Button 2 Label
|
||||
* @param {string} buttonaction2 Button 2 Action
|
||||
* @param {string} noback If true, disables the back button
|
||||
* @param {string} sound Sound to play
|
||||
* @param {string} sound Sound to play when showing the alert (default is "none")
|
||||
*/
|
||||
constructor(image = null, message = null, buttonlabel1 = null, buttonaction1 = null, buttonlabel2 = null, buttonaction2 = null, noback = null, sound = null) {
|
||||
this.message = message;
|
||||
|
||||
@@ -9,14 +9,27 @@ class WTVShenanigans {
|
||||
"DISABLE_HTML_SANITIZER": 5 // disables HTML Sanitizer, allowing all sorts of chaos in email/usenet posts and signatures
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of WTVShenanigans.
|
||||
* @param {Object} minisrv_config - The minisrv configuration object.
|
||||
*/
|
||||
constructor(minisrv_config) {
|
||||
this.minisrv_config = minisrv_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current shenanigans level set in the minisrv configuration.
|
||||
* @returns {boolean|number} The shenanigans level, or false if shenanigans are disabled.
|
||||
*/
|
||||
getShenanigansLevel() {
|
||||
return this.minisrv_config.config.shenanigans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific shenanigan is enabled based on the current shenanigans level.
|
||||
* @param {number} value - The shenanigan level to check against.
|
||||
* @returns {boolean} True if the shenanigan is enabled, false otherwise.
|
||||
*/
|
||||
checkShenanigan(value) {
|
||||
var level = this.getShenanigansLevel();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const LZSS = require("./LZSS.js");
|
||||
const WTVShared = require("./WTVShared.js")['WTVShared'];
|
||||
|
||||
const WhitespaceInstruction = {
|
||||
ADD_NONE: 0,
|
||||
@@ -278,7 +279,7 @@ class WTVTellyScriptTokenizer {
|
||||
this.tokenizeIdentifierOrConstant(checkSequence);
|
||||
}
|
||||
} else {
|
||||
// Not alphanumeric <20> try symbol sequence.
|
||||
// Not alphanumeric <20> try symbol sequence.
|
||||
this.index = currentIdx;
|
||||
checkSequence = this.buildCheckSequence(ch, "^[\\-+=<>!\\|\\&]$");
|
||||
if (this.replacements.has(checkSequence)) {
|
||||
@@ -1048,7 +1049,7 @@ class WTVTellyScriptMinifier {
|
||||
class WTVTellyScript {
|
||||
|
||||
// --- TellyScript Class ---
|
||||
/*
|
||||
/**
|
||||
* Constructs a new TellyScript object.
|
||||
* @param {Uint8Array|string} data - The TellyScript data (either packed, tokenized, or raw).
|
||||
* @param {number} dataState - One of TellyScriptState (default: PACKED).
|
||||
@@ -1064,10 +1065,14 @@ class WTVTellyScript {
|
||||
this.raw_data = null;
|
||||
this.preprocessor_definitions = preprocessor_definitions;
|
||||
this.version_minor = version_minor;
|
||||
|
||||
this.wtvshared = new WTVShared();
|
||||
this.process(data, dataState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses the tellscript data based on the current preprocessor definitions.
|
||||
* It handles directives like #ifdef, #ifndef, #if, #else, #endif.
|
||||
*/
|
||||
preprocess() {
|
||||
var definitions = this.preprocessor_definitions || {};
|
||||
// Split input into lines (handling CRLF and LF)
|
||||
@@ -1140,6 +1145,10 @@ class WTVTellyScript {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minifies the TellyScript data.
|
||||
* @returns {string} The minified TellyScript data.
|
||||
*/
|
||||
minify() {
|
||||
let minifier = new WTVTellyScriptMinifier();
|
||||
this.raw_data = minifier.minify(this);
|
||||
@@ -1148,51 +1157,18 @@ class WTVTellyScript {
|
||||
this.pack();
|
||||
}
|
||||
|
||||
ipToHex(ip) {
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) {
|
||||
throw new Error('Invalid IP address');
|
||||
}
|
||||
let num = 0;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const part = parseInt(parts[i], 10);
|
||||
if (part < 0 || part > 255) {
|
||||
throw new Error('Invalid IP address');
|
||||
}
|
||||
num = (num << 8) | part;
|
||||
}
|
||||
// Convert to unsigned 32-bit number before converting to hex
|
||||
return "0x" + (num >>> 0).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
setTemplateVars(service_name, dialin_number, DNS1IP, DNS2IP) {
|
||||
this.raw_data = this.raw_data.replaceAll("%ServiceName%", service_name);
|
||||
this.raw_data = this.raw_data.replaceAll("%DialinNumber%", dialin_number);
|
||||
this.raw_data = this.raw_data.replaceAll("%DNSIP1%", DNS1IP);
|
||||
this.raw_data = this.raw_data.replaceAll("%DNSIP2%", DNS2IP);
|
||||
this.raw_data = this.raw_data.replaceAll("%DNS1%", this.ipToHex(DNS1IP));
|
||||
this.raw_data = this.raw_data.replaceAll("%DNS2%", this.ipToHex(DNS2IP));
|
||||
this.raw_data = this.raw_data.replaceAll("%DNS1%", this.wtvshared.ipToHex(DNS1IP));
|
||||
this.raw_data = this.raw_data.replaceAll("%DNS2%", this.wtvshared.ipToHex(DNS2IP));
|
||||
}
|
||||
|
||||
// --- Big Endian Converter Helpers ---
|
||||
|
||||
toUint32(bytes, offset) {
|
||||
return (
|
||||
(bytes[offset] << 24) >>> 0 |
|
||||
(bytes[offset + 1] << 16) |
|
||||
(bytes[offset + 2] << 8) |
|
||||
(bytes[offset + 3])
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
uint32ToBytes(num) {
|
||||
return [
|
||||
(num >>> 24) & 0xff,
|
||||
(num >>> 16) & 0xff,
|
||||
(num >>> 8) & 0xff,
|
||||
num & 0xff,
|
||||
];
|
||||
}
|
||||
|
||||
// --- CRC32 Calculation ---
|
||||
|
||||
@@ -1253,7 +1229,7 @@ class WTVTellyScript {
|
||||
autoDetectState(data) {
|
||||
if (data instanceof Uint8Array) {
|
||||
if (data.length > 4) {
|
||||
const magic = this.toUint32(data, 0);
|
||||
const magic = this.wtvshared.toUint32(data, 0);
|
||||
if (magic === 0x414e4459) { // "ANDY"
|
||||
this.tellyscript_type = TellyScriptType.ORIGINAL;
|
||||
return TellyScriptState.PACKED;
|
||||
@@ -1312,19 +1288,23 @@ class WTVTellyScript {
|
||||
}
|
||||
|
||||
// --- Unpacking ---
|
||||
/**
|
||||
* Unpacks the packed TellyScript data
|
||||
* @returns {Uint8Array} The unpacked TellyScript data
|
||||
*/
|
||||
unpack() {
|
||||
// Read header fields from the first 36 bytes.
|
||||
const headerBytes = this.packed_data.slice(0, PACKED_TELLYSCRIPT_HEADER_SIZE);
|
||||
this.packed_header = {
|
||||
magic: String.fromCharCode(...headerBytes.slice(0, 4)),
|
||||
version_major: this.toUint32(headerBytes, 4),
|
||||
version_minor: this.toUint32(headerBytes, 8),
|
||||
script_id: this.toUint32(headerBytes, 12),
|
||||
script_mod: this.toUint32(headerBytes, 16),
|
||||
compressed_data_length: this.toUint32(headerBytes, 20),
|
||||
decompressed_data_length: this.toUint32(headerBytes, 24),
|
||||
decompressed_checksum: this.toUint32(headerBytes, 28),
|
||||
unknown1: this.toUint32(headerBytes, 32),
|
||||
version_major: this.wtvshared.toUint32(headerBytes, 4),
|
||||
version_minor: this.wtvshared.toUint32(headerBytes, 8),
|
||||
script_id: this.wtvshared.toUint32(headerBytes, 12),
|
||||
script_mod: this.wtvshared.toUint32(headerBytes, 16),
|
||||
compressed_data_length: this.wtvshared.toUint32(headerBytes, 20),
|
||||
decompressed_data_length: this.wtvshared.toUint32(headerBytes, 24),
|
||||
decompressed_checksum: this.wtvshared.toUint32(headerBytes, 28),
|
||||
unknown1: this.wtvshared.toUint32(headerBytes, 32),
|
||||
};
|
||||
|
||||
// Extract compressed data from the remainder of the packed_data.
|
||||
@@ -1337,21 +1317,30 @@ class WTVTellyScript {
|
||||
return this.tokenized_data;
|
||||
}
|
||||
|
||||
// --- Detokenization ---
|
||||
/**
|
||||
* Detokenizes the tokenized TellyScript data
|
||||
* @returns {string} The detokenized TellyScript data
|
||||
*/
|
||||
detokenize() {
|
||||
// Uses the previously defined TellyScriptDetokenizer class.
|
||||
this.raw_data = new WTVTellyScriptDetokenizer(this.tokenized_data).detokenize();
|
||||
return this.raw_data;
|
||||
}
|
||||
|
||||
// --- Tokenization ---
|
||||
/**
|
||||
* Tokenizes the raw TellyScript data
|
||||
* @returns {Uint8Array} The tokenized TellyScript data
|
||||
*/
|
||||
tokenize() {
|
||||
// Uses the previously defined TellyScriptTokenizer class.
|
||||
this.tokenized_data = new WTVTellyScriptTokenizer(this.raw_data).tokenize();
|
||||
return this.tokenized_data;
|
||||
}
|
||||
|
||||
// --- Packing ---
|
||||
/**
|
||||
* Packs the tokenized TellyScript data into a packed format
|
||||
* @returns {Uint8Array} The packed TellyScript data
|
||||
*/
|
||||
pack() {
|
||||
// Compress tokenized data using LZSS.
|
||||
const comp = new LZSS();
|
||||
@@ -1399,14 +1388,14 @@ class WTVTellyScript {
|
||||
buffer[i] = header.magic.charCodeAt(i);
|
||||
}
|
||||
// Next fields: each 4 bytes in Big Endian order.
|
||||
buffer.set(this.uint32ToBytes(header.version_major), 4);
|
||||
buffer.set(this.uint32ToBytes(header.version_minor), 8);
|
||||
buffer.set(this.uint32ToBytes(header.script_id), 12);
|
||||
buffer.set(this.uint32ToBytes(header.script_mod), 16);
|
||||
buffer.set(this.uint32ToBytes(header.compressed_data_length), 20);
|
||||
buffer.set(this.uint32ToBytes(header.decompressed_data_length), 24);
|
||||
buffer.set(this.uint32ToBytes(header.decompressed_checksum), 28);
|
||||
buffer.set(this.uint32ToBytes(header.unknown1), 32);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.version_major), 4);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.version_minor), 8);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.script_id), 12);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.script_mod), 16);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.compressed_data_length), 20);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.decompressed_data_length), 24);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.decompressed_checksum), 28);
|
||||
buffer.set(this.wtvshared.uint32ToBytes(header.unknown1), 32);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user