diff --git a/.vscodeignore b/.vscodeignore index 16021362..f227a4a6 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -5,5 +5,4 @@ vsc-extension-quickstart.md **/jsconfig.json **/*.map **/.eslintrc.json -**/update_notes.txt -node_modules/** \ No newline at end of file +**/update_notes.txt \ No newline at end of file diff --git a/commands/convertVideoToGif.js b/commands/convertVideoToGif.js new file mode 100644 index 00000000..37acb059 --- /dev/null +++ b/commands/convertVideoToGif.js @@ -0,0 +1,143 @@ +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const ffmpeg = require('fluent-ffmpeg'); +const ffmpegPath = require('ffmpeg-static'); + +const { getConfig } = require('../support_files/config'); +const { rootModPath } = getConfig(); + +console.log("FFmpeg Path:", ffmpegPath); +console.log("Type of FFmpeg Path:", typeof ffmpegPath); + +if (typeof ffmpegPath === 'string') { + ffmpeg.setFfmpegPath(ffmpegPath); +} else { + console.error("FFmpeg Path is not a string"); +} + +// Command to open the video conversion webview +const convertVideoToGifCommand = vscode.commands.registerCommand('bg3-mod-helper.convertVideoToGif', async function () { + const panel = vscode.window.createWebviewPanel( + 'videoConversion', + 'Video Conversion', + vscode.ViewColumn.One, + { enableScripts: true } + ); + + const videoFiles = await getAllVideoFiles(rootModPath); + + panel.webview.html = getWebviewContent(videoFiles); + + // Handle messages from the webview + panel.webview.onDidReceiveMessage(async message => { + switch (message.command) { + case 'convert': + await convertVideoToGifFile(message.videoPath, message.gifPath); + break; + case 'convertAll': + for (const video of videoFiles) { + const gifPath = video.replace(path.extname(video), '.gif'); + await convertVideoToGifFile(video, gifPath); + } + break; + case 'selectFile': + const options = { + canSelectMany: false, + openLabel: 'Select a video file', + filters: { 'Video files': ['mp4', 'mkv', 'avi', 'mov'] } + }; + const fileUri = await vscode.window.showOpenDialog(options); + if (fileUri && fileUri[0]) { + const videoPath = fileUri[0].fsPath; + const gifPath = videoPath.replace(path.extname(videoPath), '.gif'); + await convertVideoToGifFile(videoPath, gifPath); + } + break; + } + }); +}); + +async function getAllVideoFiles(dir) { + let files = []; + const items = await fs.promises.readdir(dir, { withFileTypes: true }); + for (const item of items) { + const fullPath = path.join(dir, item.name); + if (item.isDirectory()) { + files = files.concat(await getAllVideoFiles(fullPath)); + } else if (/\.(mp4|mkv|avi|mov)$/i.test(item.name)) { + files.push(fullPath); + } + } + return files; +} + +function getWebviewContent(videoFiles) { + const videoFileItems = videoFiles.map(file => ` + + ${path.basename(file)} + ${file} + + + `).join(''); + + return ` + + +

Video Conversion

+ + + + + + + ${videoFileItems} +
NamePathAction
+ + + + + + `; +} + +async function convertVideoToGifFile(inputPath, outputPath) { + return new Promise((resolve, reject) => { + const normalizedInput = path.normalize(inputPath); + const normalizedOutput = path.normalize(outputPath); + + console.log("Normalized Input Path:", normalizedInput); + console.log("Normalized Output Path:", normalizedOutput); + + ffmpeg(normalizedInput) + .outputOptions([ + '-vf', 'fps=24', // Increase fps for smoother animation + '-gifflags', 'transdiff', + '-y', // Overwrite output files without asking + '-q:v', '5' // Set quality level (lower is better, 0 is the best quality) + ]) + .save(normalizedOutput) + .on('end', () => { + vscode.window.showInformationMessage(`GIF created: ${normalizedOutput}`); + resolve(); + }) + .on('error', (err) => { + vscode.window.showErrorMessage(`Error: ${err.message}`); + reject(err); + }); + }); +} + + +module.exports = { convertVideoToGifCommand }; diff --git a/commands/folderShortcuts.js b/commands/folderShortcuts.js index 928ee2ff..3b6ae1ee 100644 --- a/commands/folderShortcuts.js +++ b/commands/folderShortcuts.js @@ -49,4 +49,16 @@ const openWorkspaceFolderCommand = vscode.commands.registerCommand('bg3-mod-help } }); -module.exports = { openModsFolderCommand, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand }; \ No newline at end of file +const openPlayerProfilesFolderCommand = vscode.commands.registerCommand('bg3-mod-helper.openPlayerProfilesFolder', async () => { + const appDataPath = process.env.LOCALAPPDATA; + const playerProfilesPath = path.join(appDataPath, 'Larian Studios', "Baldur's Gate 3", 'PlayerProfiles'); + + try { + await fs.access(playerProfilesPath); + vscode.env.openExternal(vscode.Uri.file(playerProfilesPath)); + } catch (error) { + vscode.window.showInformationMessage('PlayerProfiles folder not found.'); + } +}); + +module.exports = { openModsFolderCommand, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand, openPlayerProfilesFolderCommand }; \ No newline at end of file diff --git a/commands/indentXmlFiles.js b/commands/indentXmlFiles.js new file mode 100644 index 00000000..740cd4ec --- /dev/null +++ b/commands/indentXmlFiles.js @@ -0,0 +1,64 @@ +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const { raiseError, raiseInfo } = require('../support_files/log_utils'); +const { getConfig, loadConfigFile, setModName, setConfig } = require('../support_files/config'); +const builder = require('xmlbuilder'); + +async function indentXmlFiles() { + const config = getConfig(); + const rootModPath = config.rootModPath; + const indentLevel = await vscode.window.showInputBox({ + prompt: 'Enter the number of spaces for indentation', + validateInput: (value) => { + const num = parseInt(value, 10); + return isNaN(num) || num < 0 ? 'Please enter a valid non-negative number' : null; + } + }); + + if (!indentLevel) { + return; // User cancelled the input box + } + + const findFiles = (dir, extensions, fileList = []) => { + fs.readdirSync(dir).forEach(file => { + const filePath = path.join(dir, file); + if (fs.statSync(filePath).isDirectory()) { + // Skip the Localization folder + if (path.basename(filePath).toLowerCase() !== 'localization') { + fileList = findFiles(filePath, extensions, fileList); + } + } else if (extensions.some(ext => filePath.endsWith(ext))) { + fileList.push(filePath); + } + }); + return fileList; + }; + + const files = findFiles(rootModPath, ['.lsx', '.lsj']); + + for (const filePath of files) { + let fileContent = fs.readFileSync(filePath, 'utf-8'); + // Remove BOM if it exists + if (fileContent.charCodeAt(0) === 0xFEFF) { + fileContent = fileContent.slice(1); + } + try { + const doc = builder.create(fileContent, { headless: true }); + const formattedContent = doc.end({ pretty: true, indent: ' '.repeat(parseInt(indentLevel, 10)) }); + fs.writeFileSync(filePath, formattedContent, 'utf-8'); + raiseInfo(`File ${filePath} has been formatted with an indent level of ${indentLevel} spaces.`); + } catch (error) { + raiseError(`Failed to process file ${filePath}: ${error.message}`); + } + } + + raiseInfo('XML files formatting process completed.'); +} + +const indentXmlFilesCommand = vscode.commands.registerCommand('bg3-mod-helper.indentXmlFilesCommand', async function () { + await indentXmlFiles(); +}); + +module.exports = { indentXmlFilesCommand }; diff --git a/commands/insertHandleUUID.js b/commands/insertHandleUUID.js index 0951354b..0fdc6571 100644 --- a/commands/insertHandleUUID.js +++ b/commands/insertHandleUUID.js @@ -94,6 +94,14 @@ let handleDisposable = vscode.commands.registerCommand('bg3-mod-helper.insertHan // Update localization files with the handle await updateLocaXmlFiles(changes); + + // Save the updated localization files + const locaFiles = await vscode.workspace.findFiles('**/Localization/**/*.xml'); + for (const locaFile of locaFiles) { + const document = await vscode.workspace.openTextDocument(locaFile); + await document.save(); // Save each localization XML file directly + } + console.log(`Handle ${handle} created with initial value: ${userText}`); } } @@ -103,7 +111,60 @@ let handleDisposable = vscode.commands.registerCommand('bg3-mod-helper.insertHan vscode.window.showInformationMessage('Handles inserted and localization files updated successfully.'); } else { console.error('Apply Edit failed:', workspaceEdit); - vscode.window.showErrorMessage('Failed to replace the UUIDs.'); + vscode.window.showErrorMessage('Failed to replace the handle.'); + } +}); + +// Command to generate and replace all handles +let handleReplaceDisposable = vscode.commands.registerCommand('bg3-mod-helper.generateReplaceAllHandles', async function () { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showInformationMessage('You need to open an editor window to use this command'); + return; + } + + const document = editor.document; + const selection = editor.selection; + const selectedText = document.getText(selection); + + // Validate the selected text as handle + const handleRegex = /^h[0-9a-fA-Fg]{32}[0-9a-fA-Fg]{4}$/i; + console.log(handleRegex) + console.log(selectedText) + if (!handleRegex.test(selectedText)) { + vscode.window.showErrorMessage('The selected text is not a valid handle.'); + return; + } + + const newHandle = generateHandle(); + const globalRegex = new RegExp(selectedText, 'gi'); // Global case-insensitive search + const workspaceEdit = new vscode.WorkspaceEdit(); + let documentsToSave = new Set(); + + // Search across all text files in the workspace + const files = await vscode.workspace.findFiles('**/*.{txt,lsx,lsj,xml}'); + for (const file of files) { + const textDoc = await vscode.workspace.openTextDocument(file); + const text = textDoc.getText(); + let match; + while ((match = globalRegex.exec(text)) !== null) { + const startPos = textDoc.positionAt(match.index); + const endPos = textDoc.positionAt(match.index + match[0].length); + const range = new vscode.Range(startPos, endPos); + workspaceEdit.replace(file, range, newHandle); + documentsToSave.add(textDoc); + } + } + + // Apply/save all collected edits + if (await vscode.workspace.applyEdit(workspaceEdit)) { + + for (const doc of documentsToSave) { + await doc.save(); + } + vscode.window.showInformationMessage(`Replaced all occurrences of the handle '${selectedText}' with '${newHandle}' and saved changes. Use undo keyboard shortcut to revert all changes back at once (dont forget to save the files if you do).`); + } else { + vscode.window.showErrorMessage('Failed to replace the handles.'); } }); @@ -122,7 +183,7 @@ async function updateLocaXmlFiles(changes) { } const locaFilePattern = new vscode.RelativePattern(workspaceFolder, '**/Localization/**/*.xml'); - const locaFiles = await vscode.workspace.findFiles(locaFilePattern, '**/node_modules/**'); + const locaFiles = await vscode.workspace.findFiles(locaFilePattern); if (locaFiles.length === 0) { vscode.window.showWarningMessage(`No .xml files found under Localization/. You can create one with the 'Create BG3 File' command.`, 'Create BG3 File').then(selection => { if (selection === 'Create BG3 File') { diff --git a/commands/launchGame.js b/commands/launchGame.js index 56a25584..70bf1db5 100644 --- a/commands/launchGame.js +++ b/commands/launchGame.js @@ -6,15 +6,17 @@ const { getConfig } = require('../support_files/config'); const launchGameCommand = vscode.commands.registerCommand('bg3-mod-helper.launchGame', function () { - const { launchContinueGame, gameInstallLocation } = getConfig(); + const { launchContinueGame, gameInstallLocation, laucherAPI } = getConfig(); if (!gameInstallLocation || gameInstallLocation === "") { vscode.window.showErrorMessage('Game installation location is not set. Please configure it correctly in settings.'); return; // Stop execution if the path is not set } + const executableName = laucherAPI === 'DirectX' ? 'bg3_dx11.exe' : 'bg3.exe'; + // Construct the path to the executable const binLocation = path.join(gameInstallLocation, 'bin'); - const gamePath = path.join(binLocation, "bg3.exe"); + const gamePath = path.join(binLocation, executableName); const args = launchContinueGame ? ["-continueGame"] : []; const game = spawn(gamePath, args, { cwd: binLocation }); diff --git a/commands/organizeDataFiles.js b/commands/organizeDataFiles.js new file mode 100644 index 00000000..1e65ac89 --- /dev/null +++ b/commands/organizeDataFiles.js @@ -0,0 +1,115 @@ +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const { raiseError, raiseInfo } = require('../support_files/log_utils'); +const { getConfig, loadConfigFile, setModName, setConfig } = require('../support_files/config'); + +function sortEntriesInFile(filePath) { + if (!fs.existsSync(filePath)) { + return null; + } + + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const entries = fileContent.split(/(?:\r\n|\r|\n)(?=new entry )/).filter(entry => entry.trim() !== ''); + + const sortedEntries = entries.map(entry => { + const lines = entry.split(/(?:\r\n|\r|\n)/).filter(line => line.trim() !== ''); + const newEntry = lines.shift(); // Extract "new entry" line + const typeLine = lines.shift(); // Extract "type" line + let usingLine = ''; + const dataLines = lines.filter(line => { + if (line.startsWith('using ')) { + usingLine = line; + return false; + } + return true; + }).sort(); // Sort data lines alphabetically + + // Construct the sorted entry + return `${newEntry}\r\n${typeLine}\r\n${usingLine ? usingLine + '\r\n' : ''}${dataLines.join('\r\n')}`; + }); + + const sortedContent = sortedEntries.join('\r\n\r\n'); + + // Write sorted content to a temporary file + const tempFilePath = path.join(os.tmpdir(), path.basename(filePath)); + fs.writeFileSync(tempFilePath, sortedContent); + + return { sortedContent, tempFilePath }; +} + +async function autoSortFiles() { + const config = getConfig(); + const rootModPath = config.rootModPath; + const modName = config.modName; + let saveAll = false; + let closeRemaining = false; + + const statsPath = path.join(rootModPath, 'Public', modName, 'Stats', 'Generated', 'Data'); + + if (!fs.existsSync(statsPath)) { + raiseError(`Data directory does not exist: ${statsPath}`); + vscode.window.showErrorMessage(`Data directory does not exist: ${statsPath}`); + return; + } + + fs.readdir(statsPath, async (err, files) => { + if (err) { + raiseError(`Failed to read directory: ${err.message}`); + vscode.window.showErrorMessage(`Failed to read directory: ${err.message}`); + return; + } + + for (const file of files) { + if (closeRemaining) { + break; + } + + const filePath = path.join(statsPath, file); + const result = sortEntriesInFile(filePath); + + if (result) { + const { sortedContent, tempFilePath } = result; + const leftUri = vscode.Uri.file(filePath); + const rightUri = vscode.Uri.file(tempFilePath); + const title = `Compare: ${path.basename(filePath)}`; + + await vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title); + + if (saveAll) { + fs.writeFileSync(filePath, sortedContent); + raiseInfo(`File ${filePath} has been overwritten with sorted content.`); + } else { + const confirm = await vscode.window.showInformationMessage( + `Do you want to overwrite the original file with the sorted content for ${path.basename(filePath)}?`, + 'Save', 'Close', 'Save Remaining', 'Close Remaining' + ); + + if (confirm === 'Save') { + fs.writeFileSync(filePath, sortedContent); + raiseInfo(`File ${filePath} has been overwritten with sorted content.`); + } else if (confirm === 'Save Remaining') { + saveAll = true; + fs.writeFileSync(filePath, sortedContent); + raiseInfo(`File ${filePath} has been overwritten with sorted content.`); + } else if (confirm === 'Close Remaining') { + closeRemaining = true; + raiseInfo(`Process terminated by user.`); + return; + } else { + raiseInfo(`File ${filePath} was not overwritten.`); + } + } + } + } + + raiseInfo('File sorting process completed.'); + }); +} + +const organizeDataFiles = vscode.commands.registerCommand('bg3-mod-helper.organizeDataFilesCommand', async function () { + await autoSortFiles(); +}); + +module.exports = { organizeDataFiles }; diff --git a/commands/packMod.js b/commands/packMod.js index f79827b4..2e871d1b 100644 --- a/commands/packMod.js +++ b/commands/packMod.js @@ -14,42 +14,45 @@ const { convert } = require('../support_files/conversion_junction.js'); const { getFormats } = require('../support_files/lslib_utils.js'); const { pak } = getFormats(); +let hasPromptedForModDestPath = false; -const packModZipCommand = vscode.commands.registerCommand('bg3-mod-helper.packModZip', async function () { - // You can call the existing packMod function with additional parameters - await vscode.commands.executeCommand('bg3-mod-helper.packMod', { packAsZip: true }); -}); - -// i think we should separate out the functions here if possible- maybe put some of them in helper_functions? -const packModCommand = vscode.commands.registerCommand('bg3-mod-helper.packMod', async function (options = {}) { +const packModCommand = vscode.commands.registerCommand('bg3-mod-helper.packMod', async function (action) { bg3mh_logger.info("pack button clicked", false); - const { rootModPath, modDestPath, lslibPath, autoLaunchOnPack } = getConfig(); + const { rootModPath, modDestPath, lslibPath } = getConfig(); const modName = await getModName(); const modsDirPath = path.join(rootModPath, "Mods"); const metaPath = path.join(modsDirPath, modName, "meta.lsx"); - // Check if BG3 is running - const isRunning = await isGameRunning(); + // Check if BG3 is running might not need anymore i cant rememebr + const gameRunning = await handleGameRunning(); - if (isRunning) { - vscode.window.showErrorMessage('Baldur\'s Gate 3 is currently running. Please close the game before packing the mod.'); + if (gameRunning) { + vscode.window.showErrorMessage('Baldur\'s Gate 3 is currently running. Please close the game before packing the mod or enable autoclose in settings.'); return; // Stop further execution } - // Check if modDestPath is blank - if (!modDestPath.includes(path.join("Larian Studios", path.sep, "Baldur's Gate 3", path.sep, "Mods"))) { - const useStandardPath = await vscode.window.showInformationMessage( - 'The Mods destination path does not seem to be the standard Baldur\'s Gate 3 Mods folder. Do you want to change it?', - 'Change to Standard', 'Keep Current' - ); - if (useStandardPath === 'Change to Standard') { - const standardPath = path.join(process.env.LOCALAPPDATA, (path.join("Larian Studios", path.sep, "Baldur's Gate 3", path.sep, "Mods"))); - const modDestPath = standardPath - await vscode.workspace.getConfiguration('bg3ModHelper').update('modDestPath', standardPath, vscode.ConfigurationTarget.Global); + const workspaceState = vscode.workspace.getConfiguration('bg3ModHelper'); + const promptedForModDestPath = workspaceState.get('promptedForModDestPath', false); + + // Only show the prompt the first time + if (!hasPromptedForModDestPath) { + if (!modDestPath.includes(path.join("Larian Studios", "Baldur's Gate 3", "Mods"))) { + const useStandardPath = await vscode.window.showInformationMessage( + 'The Mods destination path does not seem to be the standard Baldur\'s Gate 3 Mods folder. Do you want to change it?', + 'Change to Standard (REQUIRES RELOAD OF VSCODE)', 'Keep Current' + ); + if (useStandardPath === 'Change to Standard (REQUIRES RELOAD OF VSCODE)') { + const standardPath = path.join(process.env.LOCALAPPDATA, "Larian Studios", "Baldur's Gate 3", "Mods"); + await vscode.workspace.getConfiguration('bg3ModHelper').update('modDestPath', standardPath, vscode.ConfigurationTarget.Global); + } + + // Set the flag to true to prevent the prompt from appearing again + hasPromptedForModDestPath = true; } } + bg3mh_logger.info("Grabbed mod name %s from %s.", modName, rootModPath); if (!fs.existsSync(metaPath)) { @@ -87,9 +90,11 @@ const packModCommand = vscode.commands.registerCommand('bg3-mod-helper.packMod', } // send the directory to the convert() function, and let it know it's a pak - await convert(rootModPath, pak); + await convert(rootModPath, pak, action); - if (autoLaunchOnPack) { + console.log(action) + if (action === 'packAndPlay') { + console.log('rrr') vscode.commands.executeCommand('bg3-mod-helper.launchGame'); } }); @@ -123,20 +128,41 @@ function createVersion64(major, minor, build, revision) { } -function isGameRunning() { +function handleGameRunning() { return new Promise((resolve, reject) => { - exec('tasklist', (error, stdout, stderr) => { + exec('tasklist', async (error, stdout, stderr) => { if (error || stderr) { - bg3mh_logger.error("Error checking running processes" + error || stderr); + bg3mh_logger.error("Error checking running processes: " + (error || stderr)); resolve(false); // Assuming game is not running in case of error return; } - - // need to add a check for linux as well - const isRunning = stdout.toLowerCase().includes('bg3.exe'); - resolve(isRunning); + const { autoCloseBG3, laucherAPI } = getConfig(); + const exeName = laucherAPI === 'DirectX' ? 'bg3_dx11.exe' : 'bg3.exe'; + + // Check if BG3 is running (add Linux check if necessary) + const isRunning = stdout.toLowerCase().includes(exeName); + + if (isRunning) { + if (autoCloseBG3) { + exec(`taskkill /F /IM ${exeName}`, (killError, killStdout, killStderr) => { + if (killError || killStderr) { + bg3mh_logger.error("Error closing Baldur's Gate 3: " + (killError || killStderr)); + resolve(false); // Return false if there was an error closing the game + return; + } + + vscode.window.showInformationMessage('Baldur\'s Gate 3 was closed to pack the mod.'); + bg3mh_logger.info("Baldur's Gate 3 was successfully closed."); + resolve(false); + }); + } else { + resolve(true); // Game is running, but user opted not to auto-close + } + } else { + resolve(false); // Game is not running + } }); }); } -module.exports = { packModZipCommand, packModCommand }; \ No newline at end of file +module.exports = { packModCommand }; diff --git a/commands/symlinker.js b/commands/symlinker.js index c2e072e4..767b0b32 100644 --- a/commands/symlinker.js +++ b/commands/symlinker.js @@ -29,9 +29,11 @@ const symlinkCommand = vscode.commands.registerCommand('bg3-mod-helper.symlinker // Handle localization directories const localizationPath = path.join(rootModPath, 'Localization'); - const locDirectories = fs.readdirSync(localizationPath, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory()) - .map(dirent => dirent.name); + const locDirectories = fs.existsSync(localizationPath) + ? fs.readdirSync(localizationPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + : []; const selectedLocDirs = await vscode.window.showQuickPick(locDirectories, { canPickMany: true, @@ -47,7 +49,7 @@ const symlinkCommand = vscode.commands.registerCommand('bg3-mod-helper.symlinker console.log("Paths to create symlinks for:", paths); - let anyExists = Object.values(paths).some(p => fs.existsSync(p)); + let anyExists = Object.values(paths).some(p => fs.existsSync(p) && !p.includes('GustavDev')); if (anyExists) { const response = await vscode.window.showWarningMessage( @@ -59,7 +61,7 @@ const symlinkCommand = vscode.commands.registerCommand('bg3-mod-helper.symlinker return; } else if (response === 'Remove All' || response === 'Replace All') { Object.values(paths).forEach(p => { - if (fs.existsSync(p)) { + if (fs.existsSync(p) && !p.includes('GustavDev')) { console.log("Removing existing path:", p); fs.rmdirSync(p, { recursive: true }); } @@ -81,6 +83,13 @@ const symlinkCommand = vscode.commands.registerCommand('bg3-mod-helper.symlinker } catch (error) { vscode.window.showErrorMessage(`Failed to create symlinks: ${error.message}`); } + + // Special handling for the GustavDev folder + const modsPath = path.join(rootModPath, 'Mods'); + const gustavDevPath = path.join(modsPath, 'GustavDev'); + if (fs.existsSync(gustavDevPath)) { + console.log("Ignoring GustavDev folder in Mods directory."); + } }); module.exports = symlinkCommand; diff --git a/extension.js b/extension.js index a766422a..be29ea3c 100644 --- a/extension.js +++ b/extension.js @@ -34,6 +34,12 @@ let packModImport, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand, + openPlayerProfilesFolderCommand, + organizeDataFilesCommand, + xmlMergerCommand, + symlinkerCommand, + indentXmlFilesCommand, + convertVideoToGifCommand, debugCommand, debug2Command, unpackGameDataCommand, @@ -82,6 +88,7 @@ function setCommands() { openGameFolderCommand = require('./commands/folderShortcuts').openGameFolderCommand; openLogsFolderCommand = require('./commands/folderShortcuts').openLogsFolderCommand; openWorkspaceFolderCommand = require('./commands/folderShortcuts').openWorkspaceFolderCommand; + openPlayerProfilesFolderCommand = require('./commands/folderShortcuts').openPlayerProfilesFolderCommand; // image commands resizeImageTooltip = require('./commands/resizeImage').resizeImageTooltip; @@ -97,6 +104,13 @@ function setCommands() { // launch the game launchGameImport = require('./commands/launchGame'); + // Need to be placed somewhere + xmlMergerCommand = require('./commands/xmlMerger'); + organizeDataFilesCommand = require('./commands/organizeDataFiles'); + symlinkerCommand = require('./commands/symlinker'); + indentXmlFilesCommand = require('./commands/indentXmlFiles'); + convertVideoToGifCommand = require('./commands/convertVideoToGif'); + // debug commands debugCommand = require('./commands/debug'); debug2Command = require('./commands/debug2'); @@ -186,7 +200,7 @@ 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(vscode.commands.registerCommand('bg3-mod-helper.removeFromExcludeList', removeFromExcludeList)); - context.subscriptions.push(uuidsHandlesHoverProvider, functionsHoverProvider, DDSToPNGCommand, PNGToDDSCommand, resizeTooltipCommand, resizeControllerCommand, resizeHotbarCommand, resizeCustomCommand, createModTemplateCommand, addIconBackgroundCommand, openConverterCommand, versionGeneratorCommand, rotationToolCommand, openModsFolderCommand, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand); + context.subscriptions.push(uuidsHandlesHoverProvider, functionsHoverProvider, DDSToPNGCommand, PNGToDDSCommand, resizeTooltipCommand, resizeControllerCommand, resizeHotbarCommand, resizeCustomCommand, createModTemplateCommand, addIconBackgroundCommand, openConverterCommand, versionGeneratorCommand, rotationToolCommand, openModsFolderCommand, openGameFolderCommand, openLogsFolderCommand, openWorkspaceFolderCommand, openPlayerProfilesFolderCommand); } @@ -209,13 +223,14 @@ function aSimpleDataProvider() { { label: 'Pack/Unpacking Tool (Click arrow for quick actions, or text to open the tool[tool is in development])', id: 'packer' }, { label: 'Conversion Tool (Click arrow for quick actions, or text to open the tool)', command: 'bg3-mod-helper.openConverter', id: 'conversion' }, { label: 'Configuration Options', id: 'config' }, + { label: 'File Formatting/Cleaning', id: 'formatting' }, { label: 'Launch Game', command: 'bg3-mod-helper.launchGame' }, { label: 'Generate Folder Structure', command: 'bg3-mod-helper.createModTemplate' }, { label: 'Atlas Generator (Supply a folder of icons to make an atlas and its corresponding .dds with those icons, as well as its merged.lsx)', command: 'bg3-mod-helper.createAtlas' }, { label: 'BBCode/Markdown Editor ', command: 'bg3-mod-helper.textEditorTool'}, + { label: 'Convert Video to GIF', command: 'bg3-mod-helper.convertVideoToGif' }, { label: 'Version Generator', command: 'bg3-mod-helper.versionGenerator' }, { label: 'Merge Xmls', command: 'bg3-mod-helper.xmlMerger' }, - { label: 'Add Dependencies to Meta via modsettings.lsx', command: 'bg3-mod-helper.addDependencies'}, { label: 'Add/Remove Symlink (in development)', command: 'bg3-mod-helper.symlinker' }, { label: 'Rotation Tool (in development)', command: 'bg3-mod-helper.rotationTool' }, { label: 'DDS Viewer (in development)', command: 'bg3-mod-helper.DDSViewer' }, @@ -226,11 +241,17 @@ function aSimpleDataProvider() { ]); } else if (element.id === 'packer') { return Promise.resolve([ - { label: 'Pack Mod', command: 'bg3-mod-helper.packMod' }, - { label: 'Pack Mod and ZIP(gz)', command: 'bg3-mod-helper.packModZip' }, + { label: 'Pack Mod', command: 'bg3-mod-helper.packMod', arguments: 'pack' }, + { label: 'Pack and Play Mod', command: 'bg3-mod-helper.packMod', arguments: 'packAndPlay' }, + { label: 'Pack and Zip Mod', command: 'bg3-mod-helper.packMod', arguments: 'packAndZip' }, { label: 'Unpack Mod', command: 'bg3-mod-helper.unpackMod' }, { label: 'Unpack Game Data (in development)', command: 'bg3-mod-helper.unpackGameDataCommand' } ]); + } else if (element.id === 'formatting') { + return Promise.resolve([ + { label: 'Organize Data Files (Alphabetically)', command: 'bg3-mod-helper.organizeDataFilesCommand' }, + { label: 'Mass Indent LSX Files (Not working)', command: 'bg3-mod-helper.indentXmlFilesCommand'} + ]); } else if (element.id === 'config') { return Promise.resolve([ { label: 'Reload Window', command: 'workbench.action.reloadWindow' }, @@ -250,6 +271,7 @@ function aSimpleDataProvider() { { label: 'Workspace Folder', command: 'bg3-mod-helper.openWorkspaceFolder' }, { label: 'Extension Logs Folder', command: 'bg3-mod-helper.openLogsFolder' }, { label: 'Game Data Folder', command: 'bg3-mod-helper.openGameFolder' }, + { label: 'Player Profiles Folder', command: 'bg3-mod-helper.openPlayerProfilesFolder' }, ]); } else { return Promise.resolve([]); diff --git a/package-lock.json b/package-lock.json index 5d04be4c..7ac82e18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,16 @@ { "name": "bg3-mod-helper", - "version": "2.2.50", + "version": "2.2.57", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bg3-mod-helper", - "version": "2.2.50", + "version": "2.2.57", "license": "LGPL-3.0-or-later", "dependencies": { + "ffmpeg-static": "^5.2.0", + "fluent-ffmpeg": "^2.1.3", "jszip": "^3.10.1", "log4js": "^6.9.1", "magickwand.js": "^1.1.0", @@ -23,6 +25,20 @@ "vscode": "^1.86.0" } }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", + "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", @@ -200,6 +216,11 @@ "node": ">=14" } }, + "node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -269,6 +290,11 @@ "node": ">=10" } }, + "node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -284,6 +310,11 @@ "concat-map": "0.0.1" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, "node_modules/cacache": { "version": "18.0.2", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.2.tgz", @@ -376,6 +407,11 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -435,6 +471,20 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -598,11 +648,54 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/ffmpeg-static": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz", + "integrity": "sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==", + "hasInstallScript": true, + "dependencies": { + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, + "node_modules/fluent-ffmpeg": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", + "dependencies": { + "async": "^0.2.9", + "which": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fluent-ffmpeg/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/fluent-ffmpeg/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -762,6 +855,14 @@ "node": ">= 14" } }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dependencies": { + "@types/node": "^10.0.3" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1431,6 +1532,11 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1484,6 +1590,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -1785,6 +1899,11 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "license": "0BSD" }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", diff --git a/package.json b/package.json index 0f46c59c..2e191857 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.2.50", + "version": "2.2.58", "icon": "media/marketplace_icon.png", "engines": { "vscode": "^1.86.0" @@ -221,8 +221,8 @@ "category": "BG3 Mod Helper" }, { - "command": "bg3-mod-helper.packModZip", - "title": "Pack Mod and ZIP", + "command": "bg3-mod-helper.generateReplaceAllHandles", + "title": "Generate and Replace All Handles", "category": "BG3 Mod Helper" }, { @@ -254,11 +254,6 @@ "default": true, "description": "Enable or disable hover feature." }, - "bg3ModHelper.hoverShowPath": { - "type": "boolean", - "default": true, - "description": "Toggle file path in hover message (not working in current update)." - }, "bg3ModHelper.rootModPath": { "type": "string", "default": "", @@ -274,16 +269,6 @@ "default": "", "description": "(Requires reload- Configuration Options > Reload Window) Path where your LSLib.dll file lives, ie [C:\\Documents\\ExportTool-v1.19.5\\Packed]:" }, - "bg3ModHelper.autoLaunchOnPack": { - "type": "boolean", - "default": false, - "description": "(Requires reload- Configuration Options > Reload Window) Automatically launch the game after you pack your mod" - }, - "bg3ModHelper.zipOnPack": { - "type": "boolean", - "default": false, - "description": "Zip up your mod in a .gz (.gzip) format, for distribution on other platforms." - }, "bg3ModHelper.launchContinueGame": { "type": "boolean", "default": false, @@ -291,7 +276,7 @@ }, "bg3ModHelper.excludedFiles": { "type": "array", - "description": "List of files to exclude from conversion. You can quick add items to this via right click on a file in the file tree -> Add to Conversion Exclusion List. Example: [c:/path/to/ur/file.lsx]", + "description": "When you pack a mod, the bg3 mod helper will autoconvert your lsx and xml files. If you wish to exclude an lxs/xml for some reason from auto convertin, add it to the list below. You can quick add items to this via right click on a file in the file tree -> Add to Conversion Exclusion List. Example: [c:/path/to/ur/file.lsx]", "default": [], "items": { "type": "string" @@ -315,7 +300,18 @@ "bg3ModHelper.excludeHidden": { "type": "boolean", "default": true, - "description": "If, when packing mods, files and folders starting with a '.' should be excluded, ie the '.vscode' folder or a .gitignore file." + "description": "If, when packing mods, files and folders starting with a '.' should be excluded, ie the '.vscode' folder or a .gitignore file (not enabled atm)." + }, + "bg3ModHelper.autoCloseBG3": { + "type": "boolean", + "default": false, + "description": "Automatically close Baldur's Gate 3 if it is running before packing a mod." + }, + "bg3ModHelper.laucherAPI": { + "type": "string", + "default": "DirectX", + "enum": ["DirectX", "Vulkan"], + "description": "Lauch using directx or vulkan" } } }, @@ -439,6 +435,11 @@ "when": "editorTextFocus && editorHasSelection", "command": "bg3-mod-helper.generateReplaceAllUUIDs", "group": "navigation@0" + }, + { + "when": "editorTextFocus && editorHasSelection", + "command": "bg3-mod-helper.generateReplaceAllHandles", + "group": "navigation@0" } ], "bg3ModHelper.exportToolsSubmenu": [ @@ -514,6 +515,8 @@ "dependencies": "npm install" }, "dependencies": { + "ffmpeg-static": "^5.2.0", + "fluent-ffmpeg": "^2.1.3", "jszip": "^3.10.1", "log4js": "^6.9.1", "magickwand.js": "^1.1.0", diff --git a/support_files/config.js b/support_files/config.js index 3ec6aaab..2aeac71d 100644 --- a/support_files/config.js +++ b/support_files/config.js @@ -91,20 +91,19 @@ function getConfig() { return { hoverMaxFiles: config.get('hoverMaxFiles'), hoverEnabled: config.get('hoverEnabled'), - hoverShowPath: config.get('hoverShowPath'), maxCacheSize: config.get('maxCacheSize'), rootModPath: path.normalize(config.get('rootModPath')), modDestPath: path.normalize(config.get('modDestPath')), lslibPath: path.normalize(config.get('lslibPath')), - autoLaunchOnPack: config.get('autoLaunchOnPack'), - zipOnPack: config.get('zipOnPack'), launchContinueGame: config.get('launchContinueGame'), addHandlesToAllLocas: config.get('addHandlesToAllLocas'), excludedFiles: normalizeExcludedFiles(config.get('excludedFiles')), gameInstallLocation: path.normalize(config.get('gameInstallLocation')), modName: config.get('modName'), excludeHidden: config.get('excludeHidden'), - packingPriority: config.get('packingPriority') + packingPriority: config.get('packingPriority'), + autoCloseBG3: config.get('autoCloseBG3'), + laucherAPI: config.get('laucherAPI') }; } diff --git a/support_files/conversion_junction.js b/support_files/conversion_junction.js index 950493a1..a26742a7 100644 --- a/support_files/conversion_junction.js +++ b/support_files/conversion_junction.js @@ -152,7 +152,7 @@ function getDynamicPath(filePath) { // at the moment this has all the functionality i planned for it, ie lsf, loca, and paks. for any other conversions we can make separate functions -async function convert(convertPath, targetExt = path.extname(getDynamicPath(convertPath))) { +async function convert(convertPath, targetExt = path.extname(getDynamicPath(convertPath)), action = null) { let rootModPath = getConfig.rootModPath; // checks if the convertPath was undefined and halts the function before it goes any further @@ -171,14 +171,14 @@ async function convert(convertPath, targetExt = path.extname(getDynamicPath(conv await convert(rootModPath, lsx) .then(() => bg3mh_logger.info(`lsx conversion done`, false)); - processPak(rootModPath); + processPak(rootModPath, action); } // has a check for main thread here because when a worker thread calls this function, it's batch unpacking and has a specific place it needs the files inside to go, but can't rely on vscode module functions to get them else if (fs.statSync(convertPath).isFile()) { if (isMainThread) { - processPak(convertPath); + processPak(convertPath, action); } else { - processPak(convertPath, workerData.jobDestPath); + processPak(convertPath, action, workerData.jobDestPath); } } } diff --git a/support_files/gzip_functions.js b/support_files/gzip_functions.js deleted file mode 100644 index e9bb9780..00000000 --- a/support_files/gzip_functions.js +++ /dev/null @@ -1,61 +0,0 @@ -const path = require('path'); -const fs = require('fs'); - -const { isMainThread, workerData } = require('worker_threads'); - -const { createGzip } = require('zlib'); -const { pipeline } = require('stream'); -const { promisify } = require('util'); -const streamPipeline = promisify(pipeline); - - - -const { pak } = require('./lslib_utils').getFormats(); - -const { CREATE_LOGGER } = require('./log_utils'); -const bg3mh_logger = CREATE_LOGGER(); - - -let vscode, - getConfig; - -if (isMainThread) { - vscode = require('vscode'); - getConfig = require('./config.js').getConfig(); - -} else { - getConfig = workerData.workerConfig; -} - -let zipOnPack = getConfig.zipOnPack; - -async function zipUpPak(zipPak = zipOnPack) { - let rootModPath = getConfig.rootModPath; - - const temp_folder = "\\temp_folder"; - const lastFolderName = path.basename(rootModPath); - const rootParentPath = path.dirname(rootModPath); - const temp_path = path.join(rootParentPath, temp_folder); - const modTempDestPath = path.join(temp_path, lastFolderName + pak); - - if (zipPak == true) { - console.log('zipping'); - const zipPath = path.join(rootModPath, `${lastFolderName}.pak.gz`); - const gzip = createGzip(); - const source = fs.createReadStream(modTempDestPath); - const destination = fs.createWriteStream(zipPath); - - await streamPipeline(source, gzip, destination); - bg3mh_logger.info(`Gzip file has been created at ${zipPath}`, false); - - if (isMainThread) { - vscode.window.showInformationMessage(`${lastFolderName}.pak.gz created`); - } - } else { - bg3mh_logger.info('not zipping'); - } -} - -module.exports = { zipUpPak } - - diff --git a/support_files/process_pak.js b/support_files/process_pak.js index ec680774..4b1d6914 100644 --- a/support_files/process_pak.js +++ b/support_files/process_pak.js @@ -4,7 +4,7 @@ const fs = require('fs'); const { getFormats, moveFileAcrossDevices, compatRootModPath, LOAD_LSLIB } = require('./lslib_utils'); const { pak } = getFormats(); -const { zipUpPak } = require('./gzip_functions'); +const { zipUpPak } = require('./zip_functions'); const { xmlUpdate } = require('./xml_functions'); const { isMainThread, workerData } = require('node:worker_threads'); @@ -52,14 +52,13 @@ function prepareTempDir(movedPak = false) { // btw, sometimes this will log things before others because it's async. -async function processPak(modPath, unpackLocation = path.join(path.dirname(modPath), path.basename(modPath, pak))) { +async function processPak(modPath, action, unpackLocation = path.join(path.dirname(modPath), path.basename(modPath, pak))) { await lslib_load(); var build = new LSLIB.PackageBuildData(); var Packager = new LSLIB.Packager(); let rootModPath, modDestPath, - zipOnPack, packingPriority; if (isMainThread) { @@ -71,7 +70,6 @@ async function processPak(modPath, unpackLocation = path.join(path.dirname(modPa rootModPath = getConfig.rootModPath; modDestPath = getConfig.modDestPath; - zipOnPack = getConfig.zipOnPack; packingPriority = getConfig.packingPriority; build.ExcludeHidden = getConfig.excludeHidden; @@ -103,7 +101,9 @@ async function processPak(modPath, unpackLocation = path.join(path.dirname(modPa vscode.window.showInformationMessage(`${lastFolderName + pak} packed`); } - zipUpPak(zipOnPack); + if (action === 'packAndZip') { + await zipUpPak(); + } moveFileAcrossDevices(modTempDestPath, modFinalDestPath); prepareTempDir(true); diff --git a/support_files/release_notes.js b/support_files/release_notes.js index 7d786e49..73c7c971 100644 --- a/support_files/release_notes.js +++ b/support_files/release_notes.js @@ -1,14 +1,14 @@ const vscode = require('vscode'); -// This function checks for updates and shows the release notes if there's a new version. Make sure to update the webview -//when making new releases +const showAlways = false // Keep this false on release + function checkForUpdates(context) { const extensionId = 'ghostboats.bg3-mod-helper'; const extension = vscode.extensions.getExtension(extensionId); const currentVersion = extension.packageJSON.version; const lastVersion = context.globalState.get('lastVersion'); - if (!lastVersion || lastVersion !== currentVersion) { + if (showAlways || !lastVersion || lastVersion !== currentVersion) { showUpdateNotes(currentVersion, context); context.globalState.update('lastVersion', currentVersion); } @@ -26,18 +26,92 @@ function showUpdateNotes(version, context) { } function getWebviewContent(version) { - // Dynamically create the content based on the version here, maybe will use a file later in future - const releaseNotes = `Here are the new features in version ${version}:`; + const releaseNotes = generateReleaseNotes(version); return ` - Update Info + + Update Info + +

What's New in ${version}

-

${releaseNotes}

+ ${releaseNotes} `; } +function generateReleaseNotes(version) { + const notes = [ + { + version: version, + features: [ + { + title: "Release Page Created", + details: [ + "On launch of newly downloaded version of extension, launch release notes page showing newest updates, like this one :)" + ] + }, + { + title: "Mod Setting Changes [IMPORTANT]", + details: [ + "Choose to launch via directx or vulkan in the settings", + "Setting added to close the existing bg3 instance when you pack and play for quicker launch if you forgot to close. By default it is off.", + "If you have a unique mod destination path, the prompt will now only appear one time per session to avoid having to confirm each time on pack" + ] + }, + { + title: "Zipping Fixes", + details: [ + "Zipped files appear in correct location now (your set mod destination)", + "Pak file is deleted now if zip is made" + ] + }, + { + title: "Minor Changes", + details: [ + "Atlas Fix if .lsx file doenst exist yet", + "Generate and Replace Handle option added when highlighting a handle and rightclicking", + "Generate Handle will now correctly save the xml files it adds it to", + ] + }, + { + title: "Rollback Instructions", + details: [ + "If you encounter any issues with this update, you can roll back to a previous version of the extension by following these steps:", + "1. Open the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of the window or by pressing `Ctrl+Shift+X`.", + "2. Search for 'BG3 Mod Helper' in the Extensions view.", + "3. Click the gear icon next to 'BG3 Mod Helper' and select 'Install Another Version...'.", + "4. Choose the previous version from the list to revert to that version." + ] + }, + ] + } + ]; + + const currentNotes = notes.find(note => note.version === version); + if (!currentNotes) { + return `

No specific notes for version ${version}.

`; + } + + return ``; +} + module.exports = { checkForUpdates }; diff --git a/support_files/zip_functions.js b/support_files/zip_functions.js index c4ef9713..693f44d6 100644 --- a/support_files/zip_functions.js +++ b/support_files/zip_functions.js @@ -1,66 +1,55 @@ const path = require('path'); const fs = require('fs'); - const { isMainThread, workerData } = require('worker_threads'); - -const { createGzip } = require('zlib'); -const { pipeline } = require('stream'); -const { promisify } = require('util'); -const streamPipeline = promisify(pipeline); - const JSZip = require('jszip'); - +const { promisify } = require('util'); const { pak } = require('./lslib_utils').getFormats(); - const { CREATE_LOGGER } = require('./log_utils'); const bg3mh_logger = CREATE_LOGGER(); - let vscode, getConfig; if (isMainThread) { vscode = require('vscode'); getConfig = require('./config.js').getConfig(); - } else { getConfig = workerData.workerConfig; } -let zipOnPack = getConfig.zipOnPack; - let zip = new JSZip(); -async function zipUpPak(zipPak = zipOnPack) { - if (zipPak) { - let rootModPath = getConfig.rootModPath; +async function zipUpPak(zipPak) { + let rootModPath = getConfig.rootModPath; + let modDestPath = getConfig.modDestPath; + let lastFolderName = path.basename(rootModPath); + let zipPath = path.join(modDestPath, `${lastFolderName}.zip`); + + let temp_folder = path.join(path.sep, "temp_folder"); + let pakFilePath = path.join(path.join(path.dirname(rootModPath), temp_folder), lastFolderName + pak); + + if (fs.existsSync(pakFilePath)) { + let data = await promisify(fs.readFile)(pakFilePath); + + zip.file(`${lastFolderName}.pak`, data); + + let content = await zip.generateAsync({ type: 'nodebuffer' }); + await promisify(fs.writeFile)(zipPath, content); + + bg3mh_logger.info(`Zip file has been created at ${zipPath}`, false); + + await promisify(fs.unlink)(pakFilePath); + bg3mh_logger.info(`Original .pak file has been deleted at ${pakFilePath}`, false); - let temp_folder = path.join(path.sep, "temp_folder"); - let lastFolderName = path.basename(rootModPath); - - let zipPath = path.join(rootModPath, `${lastFolderName}.pak.gz`); - let gzip = createGzip(); - let source = fs.createReadStream( - path.join( - path.join( - path.dirname(rootModPath), temp_folder - ), - lastFolderName + pak - ) - ); - let destination = fs.createWriteStream(zipPath); - - await streamPipeline(source, gzip, destination); - bg3mh_logger.info(`Gzip file has been created at ${zipPath}`, false); - if (isMainThread) { - vscode.window.showInformationMessage(`${lastFolderName}.pak.gz created`); + vscode.window.showInformationMessage(`${lastFolderName}.zip created`); } } else { - bg3mh_logger.info('not zipping'); + bg3mh_logger.error(`.pak file not found at ${pakFilePath}`); + if (isMainThread) { + vscode.window.showErrorMessage(`.pak file not found at ${pakFilePath}`); + } } } -module.exports = { zipUpPak } - - +module.exports = { zipUpPak };