From 98f1929f642db818043fa006951124087d9e58b0 Mon Sep 17 00:00:00 2001 From: Adian Kozlica Date: Fri, 5 Sep 2025 16:26:26 +0200 Subject: [PATCH 1/5] fix: check whether an iso is a windows iso --- electron-builder.json | 5 +++ scripts/dev-server.js | 1 + src/main/custom_scripts/get_iso_info.py | 33 ++++++++++++++ src/renderer/views/SetupUI.vue | 58 ++++++++++++++++++++++--- 4 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/main/custom_scripts/get_iso_info.py diff --git a/electron-builder.json b/electron-builder.json index 2fffbe1..9fa131d 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -42,6 +42,11 @@ "from": "./guest_server", "to": "guest_server", "filter": ["**/*"] + }, + { + "from": "src/main/custom_scripts", + "to": "custom_scripts", + "filter": ["**/*"] } ] } diff --git a/scripts/dev-server.js b/scripts/dev-server.js index 276b254..837391d 100644 --- a/scripts/dev-server.js +++ b/scripts/dev-server.js @@ -74,6 +74,7 @@ function restartElectron() { function copyStaticFiles() { copy('static'); + copy('custom_scripts'); } /* diff --git a/src/main/custom_scripts/get_iso_info.py b/src/main/custom_scripts/get_iso_info.py new file mode 100644 index 0000000..bbe94a7 --- /dev/null +++ b/src/main/custom_scripts/get_iso_info.py @@ -0,0 +1,33 @@ +import gi +import sys + +gi.require_version("Libosinfo", "1.0") + +from gi.repository import Libosinfo + +if __name__ == '__main__': + if len(sys.argv) < 2: + print('Missing filename!', file=sys.stderr) + exit(1) + + file_path = sys.argv[1] + + loader = Libosinfo.Loader() + loader.process_default_path() + db = loader.get_db() + + try: + media = Libosinfo.Media.create_from_location(file_path, None) + except gi.repository.GLib.GError: + print('Invalid', end='') + exit(0) + + if not media.is_bootable(): + print('Invalid', end='') + exit(0) + + if not db.identify_media(media): + print('Unknown', end='') + exit(0) + + print(media.get_os().get_family(), end='') # winnt for windows \ No newline at end of file diff --git a/src/renderer/views/SetupUI.vue b/src/renderer/views/SetupUI.vue index e4dd977..275decc 100644 --- a/src/renderer/views/SetupUI.vue +++ b/src/renderer/views/SetupUI.vue @@ -434,11 +434,22 @@ import { WINDOWS_VERSIONS, WINDOWS_LANGUAGES, type WindowsVersionKey } from "../ import { InstallManager, type InstallState, InstallStates } from '../lib/install'; import { openAnchorLink } from '../utils/openLink'; import license from '../assets/LICENSE.txt?raw' +const { spawnSync }: typeof import('child_process') = require('child_process'); const path: typeof import('path') = require('path') const electron: typeof import('electron') = require('electron').remote || require('@electron/remote'); const os: typeof import('os') = require('os'); +function getResourcesPath() { + if (electron.app.isPackaged) { + return process.resourcesPath; + } else { + return electron.app.getAppPath(); + } +} + +const PYTHON_SCRIPT_FILE = path.join(getResourcesPath(), 'custom_scripts', 'get_iso_info.py') + type Step = { id: string, title: string, @@ -531,6 +542,14 @@ onMounted(async () => { console.log("Username", username.value); }) +function getIsoType(path: string) { + const result = spawnSync('python3', [PYTHON_SCRIPT_FILE, path], { + encoding: 'utf8', + }); + + return result.stdout; +} + function selectIsoFile() { electron.dialog.showOpenDialog({ title: 'Select ISO File', @@ -544,11 +563,40 @@ function selectIsoFile() { }) .then(result => { if (!result.canceled && result.filePaths.length > 0) { - customIsoPath.value = result.filePaths[0]; - customIsoFileName.value = path.basename(result.filePaths[0]); - windowsLanguage.value = 'English'; // Language can't be custom - windowsVersion.value = 'custom'; - console.log('ISO path updated:', customIsoPath.value); + const filePath = result.filePaths[0] + const isoType = getIsoType(filePath); + + if (isoType === 'Invalid') { + electron.dialog.showErrorBox("Invalid ISO!", "This ISO is not bootable!"); + } + + else if (isoType === 'linux') { + electron.dialog.showErrorBox("Invalid ISO!", "You can not use Linux ISO files!"); + } + + else { + if (isoType === 'Unknown') { + const result = electron.dialog.showMessageBoxSync({ + type: 'warning', + title: 'Unknown Operating System', + message: 'ISO Contains Unknown OS', + detail: 'The operating system on this ISO image could not be identified. This may be due to:\n\n• Custom or modified OS build\n• Unsupported operating system\n• Corrupted or incomplete ISO file\n\nProceeding may result in unexpected behavior or system instability.', + buttons: ['Proceed Anyway', 'Cancel'], + defaultId: 1, + cancelId: 1, + }); + + if (result !== 0) { + return; + } + } + + customIsoPath.value = filePath; + customIsoFileName.value = path.basename(filePath); + windowsLanguage.value = 'English'; // Language can't be custom + windowsVersion.value = 'custom'; + console.log('ISO path updated:', customIsoPath.value); + } } }); } From ca8a6f4fcfce8f04bcd4ef2a5aaa7e080d66b808 Mon Sep 17 00:00:00 2001 From: Adian Kozlica Date: Fri, 5 Sep 2025 16:33:47 +0200 Subject: [PATCH 2/5] fix: if statement --- src/renderer/views/SetupUI.vue | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/renderer/views/SetupUI.vue b/src/renderer/views/SetupUI.vue index 275decc..cf9f470 100644 --- a/src/renderer/views/SetupUI.vue +++ b/src/renderer/views/SetupUI.vue @@ -566,12 +566,8 @@ function selectIsoFile() { const filePath = result.filePaths[0] const isoType = getIsoType(filePath); - if (isoType === 'Invalid') { - electron.dialog.showErrorBox("Invalid ISO!", "This ISO is not bootable!"); - } - - else if (isoType === 'linux') { - electron.dialog.showErrorBox("Invalid ISO!", "You can not use Linux ISO files!"); + if (isoType !== 'Unknown' && isoType !== 'winnt') { + electron.dialog.showErrorBox("Invalid ISO!", "This ISO is not valid!"); } else { From 226e44ab1ae71f830d7e3abc7cad1e20950219f4 Mon Sep 17 00:00:00 2001 From: Adian Kozlica Date: Sun, 7 Sep 2025 14:31:53 +0200 Subject: [PATCH 3/5] Check ISO with JS --- electron-builder.json | 5 -- src/main/custom_scripts/get_iso_info.py | 33 ----------- src/renderer/lib/getIsoType.ts | 78 +++++++++++++++++++++++++ src/renderer/views/SetupUI.vue | 77 ++++++++++-------------- 4 files changed, 108 insertions(+), 85 deletions(-) delete mode 100644 src/main/custom_scripts/get_iso_info.py create mode 100644 src/renderer/lib/getIsoType.ts diff --git a/electron-builder.json b/electron-builder.json index e6eef6a..9b4be3c 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -44,11 +44,6 @@ "from": "./guest_server", "to": "guest_server", "filter": ["**/*"] - }, - { - "from": "src/main/custom_scripts", - "to": "custom_scripts", - "filter": ["**/*"] } ] } diff --git a/src/main/custom_scripts/get_iso_info.py b/src/main/custom_scripts/get_iso_info.py deleted file mode 100644 index bbe94a7..0000000 --- a/src/main/custom_scripts/get_iso_info.py +++ /dev/null @@ -1,33 +0,0 @@ -import gi -import sys - -gi.require_version("Libosinfo", "1.0") - -from gi.repository import Libosinfo - -if __name__ == '__main__': - if len(sys.argv) < 2: - print('Missing filename!', file=sys.stderr) - exit(1) - - file_path = sys.argv[1] - - loader = Libosinfo.Loader() - loader.process_default_path() - db = loader.get_db() - - try: - media = Libosinfo.Media.create_from_location(file_path, None) - except gi.repository.GLib.GError: - print('Invalid', end='') - exit(0) - - if not media.is_bootable(): - print('Invalid', end='') - exit(0) - - if not db.identify_media(media): - print('Unknown', end='') - exit(0) - - print(media.get_os().get_family(), end='') # winnt for windows \ No newline at end of file diff --git a/src/renderer/lib/getIsoType.ts b/src/renderer/lib/getIsoType.ts new file mode 100644 index 0000000..177103d --- /dev/null +++ b/src/renderer/lib/getIsoType.ts @@ -0,0 +1,78 @@ +const fs: typeof import('fs') = require('fs'); + +export enum IsoType { + INVALID, + WINDOWS, + UNKNOWN +} + +const VOLUME_ID_PATTERNS = [ + // Education + + /(J_)?CEDN?A_X86FRE_/, + /(J_)?CEDN?A_X64FREE?_/, + + // Enterprise + /^(J_)?CEN?A_X86FREV_/, + /^(J_)?CENN?A_X64FREV_/, + + // Enterprise LTSB + + /(J_)?CESN?N?_X86FREV_/, + /(J_)?CESN?N?_X64FREV_/, + + // Enterprise LTSB (Eval) + + /CESE_X86FREE_/, + /CESE_X64FREE_/, + + // Standard + + /^(J_)?CCSN?A_X86FRE_/, + /^(J_)?(CCSN?A|C?CCOMA)_X64FREE?_/ +]; + +export async function getIsoType(path: string) { + const fileHandle = await fs.promises.open(path, 'r'); + + try { + const bootRecordBuffer = Buffer.alloc(2048); + await fileHandle.read(bootRecordBuffer, 0, 2048, 17 * 2048); + + const signature = bootRecordBuffer.subarray(1, 6).toString('ascii'); + console.log('Sig', signature); + + // Check if it is IS9660 + if (signature !== 'CD001') { + return IsoType.INVALID; + } + + const pvdBuffer = Buffer.alloc(2048); + await fileHandle.read(pvdBuffer, 0, 2048, 16 * 2048); + + const volumeId = pvdBuffer.subarray(40, 72).toString('ascii').trim().replace(/\0/g, ''); + + const bootCatalogSector = bootRecordBuffer.readUInt32LE(71); + const headerBuffer = Buffer.alloc(64); + + await fileHandle.read(headerBuffer, 0, 64, bootCatalogSector * 2048); + + const bootIndicator = headerBuffer.readUInt8(32); + + if (bootIndicator !== 0x88) { + return IsoType.INVALID; + } + + for (const pattern of VOLUME_ID_PATTERNS) { + if (pattern.test(volumeId)) { + return IsoType.WINDOWS; + } + } + + return IsoType.UNKNOWN; + } + + finally { + fileHandle.close(); + } +} \ No newline at end of file diff --git a/src/renderer/views/SetupUI.vue b/src/renderer/views/SetupUI.vue index cf9f470..9f7a816 100644 --- a/src/renderer/views/SetupUI.vue +++ b/src/renderer/views/SetupUI.vue @@ -434,21 +434,10 @@ import { WINDOWS_VERSIONS, WINDOWS_LANGUAGES, type WindowsVersionKey } from "../ import { InstallManager, type InstallState, InstallStates } from '../lib/install'; import { openAnchorLink } from '../utils/openLink'; import license from '../assets/LICENSE.txt?raw' -const { spawnSync }: typeof import('child_process') = require('child_process'); - const path: typeof import('path') = require('path') const electron: typeof import('electron') = require('electron').remote || require('@electron/remote'); const os: typeof import('os') = require('os'); - -function getResourcesPath() { - if (electron.app.isPackaged) { - return process.resourcesPath; - } else { - return electron.app.getAppPath(); - } -} - -const PYTHON_SCRIPT_FILE = path.join(getResourcesPath(), 'custom_scripts', 'get_iso_info.py') +import { getIsoType, IsoType } from '../lib/getIsoType'; type Step = { id: string, @@ -542,14 +531,6 @@ onMounted(async () => { console.log("Username", username.value); }) -function getIsoType(path: string) { - const result = spawnSync('python3', [PYTHON_SCRIPT_FILE, path], { - encoding: 'utf8', - }); - - return result.stdout; -} - function selectIsoFile() { electron.dialog.showOpenDialog({ title: 'Select ISO File', @@ -564,35 +545,37 @@ function selectIsoFile() { .then(result => { if (!result.canceled && result.filePaths.length > 0) { const filePath = result.filePaths[0] - const isoType = getIsoType(filePath); - - if (isoType !== 'Unknown' && isoType !== 'winnt') { - electron.dialog.showErrorBox("Invalid ISO!", "This ISO is not valid!"); - } - - else { - if (isoType === 'Unknown') { - const result = electron.dialog.showMessageBoxSync({ - type: 'warning', - title: 'Unknown Operating System', - message: 'ISO Contains Unknown OS', - detail: 'The operating system on this ISO image could not be identified. This may be due to:\n\n• Custom or modified OS build\n• Unsupported operating system\n• Corrupted or incomplete ISO file\n\nProceeding may result in unexpected behavior or system instability.', - buttons: ['Proceed Anyway', 'Cancel'], - defaultId: 1, - cancelId: 1, - }); - - if (result !== 0) { - return; + getIsoType(filePath) + .then(res => { + console.log(res); + if (res !== IsoType.UNKNOWN && res !== IsoType.WINDOWS) { + electron.dialog.showErrorBox("Invalid ISO!", "This ISO is not valid!"); + } + + else { + if (res === IsoType.UNKNOWN) { + const result = electron.dialog.showMessageBoxSync({ + type: 'warning', + title: 'Unknown Operating System', + message: 'ISO Contains Unknown OS', + detail: 'The operating system on this ISO image could not be identified. This may be due to:\n\n• Custom or modified OS build\n• Unsupported operating system\n• Corrupted or incomplete ISO file\n\nProceeding may result in unexpected behavior or system instability.', + buttons: ['Proceed Anyway', 'Cancel'], + defaultId: 1, + cancelId: 1, + }); + + if (result !== 0) { + return; + } } + + customIsoPath.value = filePath; + customIsoFileName.value = path.basename(filePath); + windowsLanguage.value = 'English'; // Language can't be custom + windowsVersion.value = 'custom'; + console.log('ISO path updated:', customIsoPath.value); } - - customIsoPath.value = filePath; - customIsoFileName.value = path.basename(filePath); - windowsLanguage.value = 'English'; // Language can't be custom - windowsVersion.value = 'custom'; - console.log('ISO path updated:', customIsoPath.value); - } + }) } }); } From a8246cc2dbcbe45d2c98ba22d951b3e10ac773a4 Mon Sep 17 00:00:00 2001 From: Adian Kozlica Date: Sun, 7 Sep 2025 14:33:18 +0200 Subject: [PATCH 4/5] Fix: remove invalid function --- scripts/dev-server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/dev-server.js b/scripts/dev-server.js index 837391d..276b254 100644 --- a/scripts/dev-server.js +++ b/scripts/dev-server.js @@ -74,7 +74,6 @@ function restartElectron() { function copyStaticFiles() { copy('static'); - copy('custom_scripts'); } /* From 5b908ed45bf7ded76021410f0b7fde9703fa7335 Mon Sep 17 00:00:00 2001 From: Adian Kozlica Date: Sun, 14 Sep 2025 22:49:27 +0200 Subject: [PATCH 5/5] use 7zip for additional checking --- package-lock.json | 11 ++++++++-- package.json | 1 + src/renderer/lib/getIsoType.ts | 37 ++++++++++++++++++++++++---------- src/renderer/views/SetupUI.vue | 23 +++------------------ 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index f48e6cd..57fa081 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "winboat", - "version": "0.7.2", + "version": "0.7.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "winboat", - "version": "0.7.2", + "version": "0.7.11", "dependencies": { "@electron/remote": "^2.1.2", "@iconify/vue": "^4.3.0", "@vueuse/core": "^13.1.0", "@vueuse/motion": "^2.2.6", + "7zip-bin-full": "^25.1.1", "apexcharts": "^4.5.0", "form-data": "^4.0.4", "jimp": "^1.6.0", @@ -1792,6 +1793,12 @@ "dev": true, "license": "MIT" }, + "node_modules/7zip-bin-full": { + "version": "25.1.1", + "resolved": "https://registry.npmjs.org/7zip-bin-full/-/7zip-bin-full-25.1.1.tgz", + "integrity": "sha512-pcujKX1IxU9d0avzX6M4SavZ+nL6QSjFKnDcHtPVuIzG2Rl+SGh+YVkg82Iy2UrMZ+IbIIuuFiRLyLYHnqWtkg==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "1.1.1", "dev": true, diff --git a/package.json b/package.json index 8d90b17..037ebe4 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@iconify/vue": "^4.3.0", "@vueuse/core": "^13.1.0", "@vueuse/motion": "^2.2.6", + "7zip-bin-full": "^25.1.1", "apexcharts": "^4.5.0", "form-data": "^4.0.4", "jimp": "^1.6.0", diff --git a/src/renderer/lib/getIsoType.ts b/src/renderer/lib/getIsoType.ts index 177103d..a915af1 100644 --- a/src/renderer/lib/getIsoType.ts +++ b/src/renderer/lib/getIsoType.ts @@ -1,10 +1,7 @@ const fs: typeof import('fs') = require('fs'); - -export enum IsoType { - INVALID, - WINDOWS, - UNKNOWN -} +const { spawnSync }: typeof import('child_process') = require('child_process'); +const sep = require('path').sep; +const path7zzs = require('7zip-bin-full').path7zzs.replace(`app.asar${sep}`, `app.asar.unpacked${sep}`); const VOLUME_ID_PATTERNS = [ // Education @@ -32,7 +29,14 @@ const VOLUME_ID_PATTERNS = [ /^(J_)?(CCSN?A|C?CCOMA)_X64FREE?_/ ]; -export async function getIsoType(path: string) { +function listIso(path: string) { + const result = spawnSync(path7zzs, ['l', '-slt', '-ba', path], { + encoding: 'utf8' + }); + return result.stdout; +} + +export async function isIsoValid(path: string) { const fileHandle = await fs.promises.open(path, 'r'); try { @@ -44,7 +48,7 @@ export async function getIsoType(path: string) { // Check if it is IS9660 if (signature !== 'CD001') { - return IsoType.INVALID; + return false; } const pvdBuffer = Buffer.alloc(2048); @@ -60,16 +64,27 @@ export async function getIsoType(path: string) { const bootIndicator = headerBuffer.readUInt8(32); if (bootIndicator !== 0x88) { - return IsoType.INVALID; + return false; } for (const pattern of VOLUME_ID_PATTERNS) { if (pattern.test(volumeId)) { - return IsoType.WINDOWS; + return true; + } + } + + const isoFiles = listIso(path); + + for (const line of isoFiles.split('\n')) { + if (line.startsWith('Path = ')) { + const cleaned = line.substring(7); + if(cleaned == 'sources/boot.wim') { + return true; + } } } - return IsoType.UNKNOWN; + return false; } finally { diff --git a/src/renderer/views/SetupUI.vue b/src/renderer/views/SetupUI.vue index 78a98f7..9cdfd22 100644 --- a/src/renderer/views/SetupUI.vue +++ b/src/renderer/views/SetupUI.vue @@ -484,7 +484,7 @@ import license from '../assets/LICENSE.txt?raw' const path: typeof import('path') = require('path') const electron: typeof import('electron') = require('electron').remote || require('@electron/remote'); const os: typeof import('os') = require('os'); -import { getIsoType, IsoType } from '../lib/getIsoType'; +import { isIsoValid } from '../lib/getIsoType'; type Step = { id: string, @@ -609,30 +609,13 @@ function selectIsoFile() { .then(result => { if (!result.canceled && result.filePaths.length > 0) { const filePath = result.filePaths[0] - getIsoType(filePath) + isIsoValid(filePath) .then(res => { - console.log(res); - if (res !== IsoType.UNKNOWN && res !== IsoType.WINDOWS) { + if (!res) { electron.dialog.showErrorBox("Invalid ISO!", "This ISO is not valid!"); } else { - if (res === IsoType.UNKNOWN) { - const result = electron.dialog.showMessageBoxSync({ - type: 'warning', - title: 'Unknown Operating System', - message: 'ISO Contains Unknown OS', - detail: 'The operating system on this ISO image could not be identified. This may be due to:\n\n• Custom or modified OS build\n• Unsupported operating system\n• Corrupted or incomplete ISO file\n\nProceeding may result in unexpected behavior or system instability.', - buttons: ['Proceed Anyway', 'Cancel'], - defaultId: 1, - cancelId: 1, - }); - - if (result !== 0) { - return; - } - } - customIsoPath.value = filePath; customIsoFileName.value = path.basename(filePath); windowsLanguage.value = 'English'; // Language can't be custom