diff --git a/commands/DDSViewer.js b/commands/DDSViewer.js new file mode 100644 index 00000000..1e54d7df --- /dev/null +++ b/commands/DDSViewer.js @@ -0,0 +1,192 @@ +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const { getConfig } = require('../support_files/config'); +const { CREATE_LOGGER } = require('../support_files/log_utils'); + +const { modName, rootModPath } = getConfig(); +const modsDirPath = path.normalize(rootModPath + "\\Mods"); +const bg3mh_logger = CREATE_LOGGER(); + + +const DDS_MAGIC = 0x20534444; +const DDSD_MIPMAPCOUNT = 0x20000; +const DDPF_FOURCC = 0x4; +const DDSCAPS2_CUBEMAP = 0x200; + +const headerLengthInt = 31; + +const off_magic = 0; +const off_size = 1; +const off_flags = 2; +const off_height = 3; +const off_width = 4; +const off_mipmapCount = 7; +const off_pfFlags = 20; +const off_pfFourCC = 21; +const off_caps2 = 28; + + +// Additional parsing functions +function fourCCToInt32(value) { + return value.charCodeAt(0) + + (value.charCodeAt(1) << 8) + + (value.charCodeAt(2) << 16) + + (value.charCodeAt(3) << 24); +} + +function int32ToFourCC(value) { + return String.fromCharCode( + value & 0xff, + (value >> 8) & 0xff, + (value >> 16) & 0xff, + (value >> 24) & 0xff + ); +} + +function parseHeaders(arrayBuffer) { + var header = new Int32Array(arrayBuffer, 0, headerLengthInt); + + if (header[off_magic] !== DDS_MAGIC) { + throw new Error('Invalid magic number in DDS header'); + } + + if (!(header[off_pfFlags] & DDPF_FOURCC)) { + throw new Error('Unsupported format, must contain a FourCC code'); + } + + var fourCC = header[off_pfFourCC]; + var format, blockBytes; + switch (fourCC) { + case fourCCToInt32('DXT1'): + blockBytes = 8; + format = 'dxt1'; + break; + case fourCCToInt32('DXT3'): + blockBytes = 16; + format = 'dxt3'; + break; + case fourCCToInt32('DXT5'): + blockBytes = 16; + format = 'dxt5'; + break; + default: + throw new Error('Unsupported FourCC code: ' + int32ToFourCC(fourCC)); + } + + var cubemap = (header[off_caps2] & DDSCAPS2_CUBEMAP) !== 0; + + return { + format: format, + width: header[off_width], + height: header[off_height], + mipMapCount: header[off_mipmapCount], + isCubemap: cubemap + }; +} + +let DDSViewerCommand = vscode.commands.registerCommand('bg3-mod-helper.DDSViewer', async function () { + bg3mh_logger.info('‾‾DDSViewerCommand‾‾'); + const panel = vscode.window.createWebviewPanel( + 'DDSViewer', + 'DDS Viewer', + vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true + } + ); + + const ddsFiles = await findDDSFiles(modsDirPath); + const ddsDetails = await Promise.all(ddsFiles.map(async file => { + const data = await fs.promises.readFile(file); + return { + path: file, + info: parseHeaders(data.buffer) + }; + })); + + panel.webview.html = getWebviewContent(ddsDetails); + + panel.webview.onDidReceiveMessage( + async message => { + try { + switch (message.command) { + case 'openFile': + vscode.commands.executeCommand('vscode.open', vscode.Uri.file(message.path)); + break; + } + } catch (err) { + panel.webview.postMessage({ command: 'alert', text: 'Error' }); + } + } + ); + bg3mh_logger.info('__DDSViewerCommand__'); +}); + +async function findDDSFiles(directory) { + let files = []; + console.log("Searching in directory: ", directory); + const entries = await fs.promises.readdir(directory, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + console.log(entry.isDirectory() ? "Directory: " + fullPath : "File: " + fullPath); + if (entry.isDirectory()) { + const subFiles = await findDDSFiles(fullPath); + files = files.concat(subFiles); + } else { + const ext = path.extname(entry.name).toLowerCase(); + console.log("File extension for " + entry.name + " is " + ext); + if (ext === '.dds') { + files.push(fullPath); + console.log("DDS file found: ", fullPath); + } + } + } + if (files.length === 0) { + console.log("No DDS files found in: ", directory); + } + return files; +} + + +function getWebviewContent(ddsDetails) { + let content = '

DDS File Viewer

'; + content += ` + + + + + + `; + + if (ddsDetails.length === 0) { + content += ''; + } else { + ddsDetails.forEach(details => { + content += ` + + + + + + `; + }); + } + content += '
File PathFormatDimensionsMipMapsCubemap
No DDS files found.
${details.path}${details.info.format}${details.info.width} x ${details.info.height}${details.info.mipMapCount}${details.info.isCubemap ? 'Yes' : 'No'}
'; + + content += ` + `; + + return content; +} + +module.exports = DDSViewerCommand; \ No newline at end of file diff --git a/commands/rotationTool.js b/commands/rotationTool.js new file mode 100644 index 00000000..406ee8e9 --- /dev/null +++ b/commands/rotationTool.js @@ -0,0 +1,103 @@ +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const { getConfig } = require('../support_files/config'); +const { CREATE_LOGGER } = require('../support_files/log_utils.js'); +const { modName, rootModPath } = getConfig(); +const modsDirPath = path.normalize(rootModPath + "\\Mods"); +const metaPath = path.normalize(modsDirPath + "\\" + modName + "\\meta.lsx"); +const bg3mh_logger = CREATE_LOGGER(); + +let rotationToolCommand = vscode.commands.registerCommand('bg3-mod-helper.rotationTool', function () { + bg3mh_logger.info('‾‾rotationToolCommand‾‾'); + const panel = vscode.window.createWebviewPanel( + 'rotationTool', + 'Rotation Tool', + vscode.ViewColumn.One, + { enableScripts: true } + ); + + panel.webview.html = getWebviewContent(); + + bg3mh_logger.info('__rotationToolCommand__'); +}); + + +function getWebviewContent() { + return ` + + + + + + Rotation Tool + + + +

Rotation Tool

+
+ + +
+
+

Input Vector (Y treated as angle in degrees)

+
+ + + +
+
+

Output (Quaternion)

+
+ + + + +
+ + + + `; +} + + + +module.exports = rotationToolCommand; diff --git a/extension.js b/extension.js index 7571d5ba..fc861e37 100644 --- a/extension.js +++ b/extension.js @@ -20,6 +20,8 @@ const { xmlToLocaCommand, locaToXmlCommand, lsxToLsfCommand, lsfToLsxCommand } = const openConverterCommand = require('./commands/openConverter'); const versionGeneratorCommand = require('./commands/versionGenerator'); +const rotationToolCommand = require('./commands/rotationTool'); +const DDSViewerCommand = require('./commands/DDSViewer'); const AutoCompleteProvider = require('./autocomplete/autoCompleteProvider'); @@ -33,10 +35,7 @@ const { resizeImageTooltip, resizeImageController, resizeImageHotbar, resizeImag const { getFullPath } = require('./support_files/helper_functions'); -/** - * Adds a file to the exclusion list. - * @param {vscode.Uri} fileUri - The URI of the file to be excluded. - */ + async function addToExcludeList(fileUri) { const config = vscode.workspace.getConfiguration('bg3ModHelper'); let excludedFiles = config.get('excludedFiles') || []; @@ -52,6 +51,19 @@ async function addToExcludeList(fileUri) { } } +async function removeFromExcludeList(fileUri) { + const config = vscode.workspace.getConfiguration('bg3ModHelper'); + let excludedFiles = config.get('excludedFiles') || []; + const filePath = fileUri.fsPath.replace(/\\/g, '/'); + + if (excludedFiles.includes(filePath)) { + excludedFiles = excludedFiles.filter(p => p !== filePath); // Remove the file from the list + await config.update('excludedFiles', excludedFiles, vscode.ConfigurationTarget.Global); + vscode.window.showInformationMessage(`${filePath} removed from conversion exclusion list.`); + } else { + vscode.window.showWarningMessage(`${filePath} not in the exclusion list.`); + } +} /** * @param {vscode.ExtensionContext} context @@ -144,7 +156,8 @@ function activate(context) { let createModTemplateCommand = vscode.commands.registerCommand('bg3-mod-helper.createModTemplate', createModTemplateImport); context.subscriptions.push(vscode.commands.registerCommand('bg3-mod-helper.addToExcludeList', addToExcludeList)); - context.subscriptions.push(uuidsHandlesHoverProvider, functionsHoverProvider, DDSToPNG, PNGToDDS, resizeTooltipCommand, resizeControllerCommand, resizeHotbarCommand, resizeCustomCommand, createModTemplateCommand, addIconBackgroundCommand, openConverterCommand, versionGeneratorCommand); + context.subscriptions.push(vscode.commands.registerCommand('bg3-mod-helper.removeFromExcludeList', removeFromExcludeList)); + context.subscriptions.push(uuidsHandlesHoverProvider, functionsHoverProvider, DDSToPNG, PNGToDDS, resizeTooltipCommand, resizeControllerCommand, resizeHotbarCommand, resizeCustomCommand, createModTemplateCommand, addIconBackgroundCommand, openConverterCommand, versionGeneratorCommand, rotationToolCommand); } @@ -171,6 +184,8 @@ function aSimpleDataProvider() { { label: 'Generate Folder Structure', command: 'bg3-mod-helper.createModTemplate' }, { label: 'Supply a folder of icons to make an atlas and its corresponding .dds with those icons', command: 'bg3-mod-helper.createAtlas' }, { label: 'Version Generator', command: 'bg3-mod-helper.versionGenerator' }, + { label: 'Rotation Tool (in development)', command: 'bg3-mod-helper.rotationTool' }, + { label: 'DDS Viewer (in development)', command: 'bg3-mod-helper.DDSViewer' }, { label: 'Debug Command', command: 'bg3-mod-helper.debugCommand' } ]); } else if (element.id === 'conversion') { diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index a4831c63..26d04f0c 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,6 +1,6 @@ { "name": "bg3-mod-helper", - "version": "2.1.31", + "version": "2.1.32", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package-lock.json b/package-lock.json index 88dec809..09d2e149 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bg3-mod-helper", - "version": "2.1.31", + "version": "2.1.32", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bg3-mod-helper", - "version": "2.1.31", + "version": "2.1.32", "license": "LGPL-3.0-or-later", "dependencies": { "@img/sharp-win32-x64": "^0.33.3", diff --git a/package.json b/package.json index 60461526..484a4b63 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "bg3_mod_helper", "publisher": "ghostboats", "description": "This extension is designed to help you make mods in Baldur's Gate 3 by creating UUIDs and handles for you, as well as updating your .loca.xml files as well should they exist. And more to come in the future.", - "version": "2.1.31", + "version": "2.1.32", "icon": "media/marketplace_icon.png", "engines": { "vscode": "^1.86.0" @@ -138,11 +138,24 @@ "command": "bg3-mod-helper.versionGenerator", "title": "Version Generator" }, + { + "command": "bg3-mod-helper.rotationTool", + "title": "Rotation Tool" + }, + { + "command": "bg3-mod-helper.DDSViewer", + "title": "DDS Viewer" + }, { "command": "bg3-mod-helper.addToExcludeList", "title": "Add to Conversion Exclusion List", "category": "BG3 Mod Helper" }, + { + "command": "bg3-mod-helper.removeFromExcludeList", + "title": "Remove from Conversion Exclusion List", + "category": "BG3 Mod Helper" + }, { "command": "bg3-mod-helper.debugCommand", "title": "For dev use, dont press the button" @@ -277,9 +290,14 @@ "group": "navigation" }, { - "when": "resourceExtname == .lsx || resourceExtname == .loca || resourceExtname == .xml || resourceExtname == .lsf", + "when": "resourceExtname == .lsx || resourceExtname == .loca || resourceExtname == .xml || resourceExtname == .lsf && !isExcluded", "command": "bg3-mod-helper.addToExcludeList", "group": "navigation" + }, + { + "when": "resourceExtname == .lsx || resourceExtname == .loca || resourceExtname == .xml || resourceExtname == .lsf && isExcluded", + "command": "bg3-mod-helper.removeFromExcludeList", + "group": "navigation" } ], "editor/context": [ diff --git a/support_files/config.js b/support_files/config.js index 90ebbda6..c3d95ecf 100644 --- a/support_files/config.js +++ b/support_files/config.js @@ -8,6 +8,18 @@ function setConfig(newConfig) { } +function normalizeExcludedFiles(excludedFiles) { + + if (Array.isArray(excludedFiles)) { + let normalizeExcludedFiles = excludedFiles.map((temp_file) => path.normalize(temp_file)); + return normalizeExcludedFiles; + } + else { + return path.normalize(excludedFiles); + } +} + + function getConfig() { const config = vscode.workspace.getConfiguration('bg3ModHelper'); return { @@ -20,7 +32,7 @@ function getConfig() { lslibPath: path.normalize(config.get('lslibPath')), autoLaunchOnPack: config.get('autoLaunchOnPack'), launchContinueGame: config.get('launchContinueGame'), - excludedFiles: config.get('excludedFiles') || [] + excludedFiles: normalizeExcludedFiles(config.get('excludedFiles')) }; } module.exports = { setConfig, getConfig }; diff --git a/support_files/conversion_junction.js b/support_files/conversion_junction.js index c22a4269..763ca428 100644 --- a/support_files/conversion_junction.js +++ b/support_files/conversion_junction.js @@ -10,7 +10,7 @@ const { CREATE_LOGGER, raiseError, raiseInfo } = require('./log_utils'); const bg3mh_logger = CREATE_LOGGER(); const { getConfig } = require('./config.js'); -const { rootModPath, modName, modDestPath, excludedFiles } = getConfig(); +const { rootModPath, modName, modDestPath } = getConfig(); const { isLoca, processLoca, getLocaOutputPath } = require('./loca_convert'); const { isLsf, processLsf, getLsfOutputPath } = require('./lsf_convert'); @@ -46,15 +46,19 @@ function convert(convertPath, targetExt = path.extname(getDynamicPath(convertPat if (targetExt === "empty") { return; } - + // we can leave this here for the moment, might be a good logic bit to keep in the future. + /* + // changed this to a const so nothing bothers it while the conversion is taking place + const { excludedFiles } = getConfig(); const normalizedExcludedFiles = excludedFiles.map(file => path.normalize(file).replace(/^([a-zA-Z]):/, (match, drive) => drive.toUpperCase() + ':')); - // bg3mh_logger.info(`Normalized Excluded Files: ${JSON.stringify(normalizedExcludedFiles, null, 2)}`); - + bg3mh_logger.info(`Normalized Excluded Files: ${JSON.stringify(normalizedExcludedFiles, null, 2)}`); + const isExcluded = (file) => { const normalizedFile = path.normalize(file).replace(/^([a-zA-Z]):/, (match, drive) => drive.toUpperCase() + ':'); return normalizedExcludedFiles.includes(normalizedFile); }; + */ if (targetExt === pak) { prepareTempDir(); @@ -67,11 +71,12 @@ function convert(convertPath, targetExt = path.extname(getDynamicPath(convertPat else if (Array.isArray(convertPath)) { for (let i = 0; i < convertPath.length; i++) { convert(convertPath[i], path.extname(convertPath[i])); + bg3mh_logger.info(`Excluded: ${convertPath[i]}`); } } else if (fs.statSync(convertPath).isDirectory()) { const filesToConvert = FIND_FILES(convertPath, targetExt); - convert(filesToConvert, targetExt); + convert(filesToConvert); } else if (fs.statSync(convertPath).isFile()) { if (isLoca(targetExt)) { @@ -91,6 +96,9 @@ function convert(convertPath, targetExt = path.extname(getDynamicPath(convertPat return; } } + } + else { + raiseInfo(`Excluded: ${convertPath}`, false); } } diff --git a/support_files/lslib_utils.js b/support_files/lslib_utils.js index bcc7c503..f28a171a 100644 --- a/support_files/lslib_utils.js +++ b/support_files/lslib_utils.js @@ -22,7 +22,7 @@ const { CREATE_LOGGER, raiseError, raiseInfo } = require('./log_utils.js') const bg3mh_logger = CREATE_LOGGER(); const { getConfig } = require('./config.js'); -const { lslibPath, excludedFiles } = getConfig(); +const { lslibPath } = getConfig(); const compatRootModPath = path.join(getConfig().rootModPath + "\\"); const lslibToolsPath = path.join(getConfig().lslibPath, TOOL_SUBDIR); @@ -149,8 +149,6 @@ function FIND_FILES(filesPath, targetExt = getFormats().lsf, isRecursive = true) recursive: isRecursive }); - bg3mh_logger.info(`Excluded Files: ${excludedFiles}`); - for (let i = 0; i < filesList.length; i++) { const temp = filesList[i].toString(); @@ -164,12 +162,12 @@ function FIND_FILES(filesPath, targetExt = getFormats().lsf, isRecursive = true) function FILTER_PATHS(filesPath) { - //raiseInfo(excludedFiles); + let excludedFiles = getConfig().excludedFiles; if (Array.isArray(filesPath)) { let filteredPaths = []; for (let i = 0; i < filesPath.length; i++) { - let temp_path = FILTER_PATHS(filesPath[i]); + let temp_path = FILTER_PATHS(path.normalize(filesPath[i])); if (temp_path) { filteredPaths.push(temp_path); @@ -183,7 +181,6 @@ function FILTER_PATHS(filesPath) { for (let i = 0; i < temp_path.length; i++) { if ((!excludedFiles.includes(filesPath) && convertDirs.includes(temp_path[i])) || (temp_ext === getFormats().dll && !illegalDlls.includes(path.basename(filesPath)))) { - bg3mh_logger.info(`${filesPath} included`); return filesPath; } }