From ff3b5f5d6cf85a3e24ad2c9498cd54ec43a334ca Mon Sep 17 00:00:00 2001 From: Nishant <46081095+ni6hant@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:13:15 +0530 Subject: [PATCH 1/5] Add GUI for ISZ to ISO conversion Implement a Tkinter GUI for ISZ to ISO conversion with file selection and progress indication. --- isz2iso_gui.py | 366 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 isz2iso_gui.py diff --git a/isz2iso_gui.py b/isz2iso_gui.py new file mode 100644 index 0000000..206a60c --- /dev/null +++ b/isz2iso_gui.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +isz2iso_gui – a tiny Tkinter wrapper around the original ISZ→ISO code. +""" +import os +import sys +import threading +import tkinter as tk +from tkinter import filedialog, messagebox, ttk + +# -------------------------------------------------------------- +# Put the original conversion classes/functions here. +# -------------------------------------------------------------- +import argparse # we keep only the serialisable parts +import bz2 +import ctypes +import zlib + +# ---- Original code starts here ---- + +class ISZ_header(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("signature", ctypes.c_char * 4), + ("header_size", ctypes.c_ubyte), + ("version_number", ctypes.c_ubyte), + ("volume_serial_number", ctypes.c_uint32), + ("sector_size", ctypes.c_uint16), + ("total_sectors", ctypes.c_uint), + ("encryption_type", ctypes.c_ubyte), + ("segment_size", ctypes.c_int64), + ("nblock", ctypes.c_uint), + ("block_size", ctypes.c_uint), + ("pointer_length", ctypes.c_ubyte), + ("file_seg_number", ctypes.c_byte), + ("chunk_pointers_offset", ctypes.c_uint), + ("segment_pointers_offset", ctypes.c_uint), + ("data_offset", ctypes.c_uint), + ("reserved", ctypes.c_ubyte), + ("checksum1", ctypes.c_uint32), + ("size1", ctypes.c_uint32), + ("unknown2", ctypes.c_uint32), + ("checksum2", ctypes.c_uint32) + ] + + password_types = { + 0: 'No password', + 1: 'Password protected', + 2: 'Encrypted AES128', + 3: 'Encrypted AES192', + 4: 'Encrypted AES256' + } + + def read_header(self, f): + if f.readinto(self) != 64: + raise Exception('Error while reading the ISZ header only got (%d bytes)' % sys.getsizeof(self)) + if self.signature != b'IsZ!': + raise Exception('Not an ISZ file (invalid signature)') + if self.version_number != 1: + raise Exception('ISZ version not supported') + + def get_uncompressed_size(self): + return self.sector_size * self.total_sectors + + def get_isz_description(self): + s = f"ISZ version {self.version_number}, {self.password_types[self.encryption_type]}" + s += f", volume serial number {hex(self.volume_serial_number)}" + s += f", uncompressed size={self.get_uncompressed_size() // 1024 // 1024} MB" + return s + + def print_isz_infos(self): + print(self.get_isz_description()) + + +class ISZ_sdt(ctypes.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("size", ctypes.c_int64), + ("number_of_chunks", ctypes.c_int32), + ("first_chunck_number", ctypes.c_int32), + ("chunk_offset", ctypes.c_int32), + ("left_size", ctypes.c_int32) + ] + + +class StorageMethods: + Zeros, Data, Zlib, Bzip2 = range(4) + + +class ISZ_File: + """ + Very small wrapper around the original ISZ file handler. + """ + def __init__(self): + self.isz_header = ISZ_header() + self.isz_segments = [] + self.chunk_pointers = [] + self.fp = None + self.filename = None + + def close_file(self): + if self.fp: + self.fp.close() + self.fp = None + self.isz_segments = [] + self.chunk_pointers = [] + + def xor_obfuscate(self, data): + code = (0xb6, 0x8c, 0xa5, 0xde) + for i in range(len(data)): + data[i] ^= code[i & 3] + return data + + def read_chunk_pointers(self): + if self.isz_header.chunk_pointers_offset == 0: + self.chunk_pointers.append((1, self.isz_header.size1)) + return + if self.isz_header.pointer_length != 3: + raise Exception('Only pointer sizes of 3 implemented') + size_bytes = self.isz_header.pointer_length * self.isz_header.nblock + self.fp.seek(self.isz_header.chunk_pointers_offset) + data = bytearray(self.fp.read(size_bytes)) + data = self.xor_obfuscate(bytearray(data)) + for i in range(self.isz_header.nblock): + chunk = data[i*3:(i+1)*3] + val = chunk[2] << 16 | chunk[1] << 8 | chunk[0] + data_type = val >> 22 + data_size = val & 0x3fffff + self.chunk_pointers.append((data_type, data_size)) + + def detect_file_naming_convention(self): + if self.filename.endswith('.isz'): + for gen in [self.name_generator_1, self.name_generator_2, self.name_generator_3]: + cand = gen(1) + if os.path.exists(cand): + self.name_generator = gen + return + raise Exception('Unable to find the naming convention used for the multi‑part ISZ file') + else: + raise Exception('For multi‑parts ISZ files, the first file need to have an .isz extension') + + def name_generator_1(self, seg_id): + if seg_id: + return self.filename[:-4] + f'.i{seg_id:02d}' + return self.filename + + def name_generator_2(self, seg_id): + return self.filename[:-11] + f'.part{seg_id+1:02d}.isz' + + def name_generator_3(self, seg_id): + return self.filename[:-12] + f'.part{seg_id+1:03d}.isz' + + def name_generator_no_change(self, seg_id): + return self.filename + + def get_segment_name(self, seg_id): + return self.name_generator(seg_id) + + def check_segment_names(self): + for i in range(len(self.isz_segments)): + if not os.path.exists(self.get_segment_name(i)): + raise Exception(f'Unable to find segment number {i}') + + def read_segment(self): + data = bytearray(self.fp.read(ctypes.sizeof(ISZ_sdt))) + data = self.xor_obfuscate(bytearray(data)) + return ISZ_sdt.from_buffer_copy(data) + + def read_segments(self): + if self.isz_header.segment_pointers_offset == 0: + seg = ISZ_sdt() + seg.size = 0 + seg.number_of_chunks = self.isz_header.nblock + seg.first_chunck_number = 0 + seg.chunk_offset = self.isz_header.data_offset + seg.left_size = 0 + self.isz_segments.append(seg) + else: + self.fp.seek(self.isz_header.segment_pointers_offset) + seg = self.read_segment() + while seg.size != 0: + self.isz_segments.append(seg) + seg = self.read_segment() + if len(self.isz_segments) > 1: + self.detect_file_naming_convention() + else: + self.name_generator = self.name_generator_no_change + self.check_segment_names() + + def open_isz_file(self, filename): + self.close_file() + self.filename = filename + self.fp = open(filename, 'rb') + self.isz_header.read_header(self.fp) + if self.isz_header.file_seg_number != 0: + raise Exception('Not the first segment in a set') + self.read_segments() + self.read_chunk_pointers() + + def read_data(self, seg_id, offset, size): + with open(self.get_segment_name(seg_id), 'rb') as fp: + fp.seek(offset) + return fp.read(size) + + def get_block(self, block_id): + block_type, block_size = self.chunk_pointers[block_id] + for seg_id, seg in enumerate(self.isz_segments): + first = seg.first_chunck_number + last = seg.first_chunck_number + seg.number_of_chunks - 1 + if first <= block_id <= last: + cur_offset = seg.chunk_offset + for i in range(first, block_id): + b_type, b_size = self.chunk_pointers[i] + if b_type != StorageMethods.Zeros: + cur_offset += b_size + size_to_read = block_size + if block_id == last and seg.left_size: + size_to_read -= seg.left_size + data = self.read_data(seg_id, cur_offset, size_to_read) + if block_id == last and seg.left_size: + data += self.read_data(seg_id+1, 64, seg.left_size) + if len(data) != block_size: + raise Exception(f'Unable to read block {block_id}') + return data + raise Exception(f'Unable to find the segment of block {block_id}') + + def decompress_block(self, block_id): + typ, size = self.chunk_pointers[block_id] + if typ == StorageMethods.Zeros: + return bytes(size) + data = self.get_block(block_id) + if typ == StorageMethods.Data: + return data + if typ == StorageMethods.Zlib: + return zlib.decompress(data) + if typ == StorageMethods.Bzip2: + data = bytearray(data) + data[0:3] = b'BZh' # restore header that was stripped + return bz2.decompress(data) + + def extract_to(self, dest_iso): + """Write the decompressed data to .""" + with open(dest_iso, 'wb') as outf: + crc = 0 + for block_id in range(len(self.chunk_pointers)): + data = self.decompress_block(block_id) + outf.write(data) + crc = zlib.crc32(data, crc) & 0xffffffff + # validate + final = (~crc) & 0xffffffff + if final != self.isz_header.checksum1: + raise Exception('CRC Error during extraction') + +# ---- Original code ends here ---- + + +# -------------------------------------------------------------- +# GUI +# -------------------------------------------------------------- +class Application(tk.Tk): + + def __init__(self): + super().__init__() + self.title('ISZ → ISO converter') + self.resizable(False, False) + + # center the dialog + self.geometry('400x200') + self.eval('tk::PlaceWindow . center') + + # vars + self.src_file = tk.StringVar() + self.dest_file = tk.StringVar() + + # layout + frm = ttk.Frame(self, padding=(10, 10, 10, 10)) + frm.grid(row=0, column=0, sticky='nsew') + frm.columnconfigure(1, weight=1) + + ttk.Label(frm, text='Source ISZ file:').grid(row=0, column=0, sticky='w') + src_entry = ttk.Entry(frm, textvariable=self.src_file, width=40) + src_entry.grid(row=0, column=1, sticky='ew', padx=(0, 5)) + ttk.Button(frm, text='Browse…', command=self.browse_src).grid(row=0, column=2) + + ttk.Label(frm, text='Destination ISO:').grid(row=1, column=0, sticky='w') + dest_entry = ttk.Entry(frm, textvariable=self.dest_file, width=40) + dest_entry.grid(row=1, column=1, sticky='ew', padx=(0, 5)) + ttk.Button(frm, text='Browse…', command=self.browse_dest).grid(row=1, column=2) + + # Show a progress bar only if we want; for now just a simple status label + self.status_lbl = ttk.Label(frm, text='Ready', anchor='center') + self.status_lbl.grid(row=2, column=0, columnspan=3, pady=(10, 0), sticky='ew') + + # convert button + ttk.Button(frm, text='Convert → ISO', command=self.convert).grid(row=3, column=0, columnspan=3, pady=(10, 0)) + + # -------------------------------------------------------------------- + def browse_src(self): + path = filedialog.askopenfilename( + title='Select source ISZ file', + filetypes=[('ISZ files', '*.isz'), ('All files', '*.*')] + ) + if path: + self.src_file.set(path) + # auto–guess destination if not already set + if not self.dest_file.get(): + guess = os.path.splitext(path)[0] + '.iso' + self.dest_file.set(guess) + + # -------------------------------------------------------------------- + def browse_dest(self): + path = filedialog.asksaveasfilename( + title='Select destination ISO', + defaultextension='.iso', + filetypes=[('ISO files', '*.iso'), ('All files', '*.*')] + ) + if path: + self.dest_file.set(path) + + # -------------------------------------------------------------------- + def convert(self): + src = self.src_file.get() + dst = self.dest_file.get() + if not src or not os.path.isfile(src): + messagebox.showerror('Error', 'Please choose a valid .isz file.') + return + if not dst: + messagebox.showerror('Error', 'Please choose a destination file.') + return + + # Disable UI while converting + self.status_lbl.config(text='Converting…') + self.update_idletasks() + + # Run conversion in a background thread so that the UI stays responsive + thread = threading.Thread(target=self._convert_worker, args=(src, dst), daemon=True) + thread.start() + + # -------------------------------------------------------------------- + def _convert_worker(self, src, dst): + try: + isz = ISZ_File() + isz.open_isz_file(src) + isz.extract_to(dst) + isz.close_file() + self.status_lbl.config(text='Done!') + messagebox.showinfo('Success', f'Converted to:\n{dst}') + except Exception as exc: + self.status_lbl.config(text='Error') + messagebox.showerror('Error', f'Failed:\n{exc}') + finally: + # re‑enable the GUI + self.after(0, self._enable_ui) + + def _enable_ui(self): + self.status_lbl.config(text='Ready') + # We could re‑enable buttons/entries if we had disabled them + +# -------------------------------------------------------------- +# MAIN +# -------------------------------------------------------------- +if __name__ == '__main__': + app = Application() + app.mainloop() From 948d3c5d44db763698ef79e78fc3084e48ad4e8d Mon Sep 17 00:00:00 2001 From: Nishant <46081095+ni6hant@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:14:56 +0530 Subject: [PATCH 2/5] Refactor GUI code for clarity and organization --- isz2iso_gui.py | 189 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 126 insertions(+), 63 deletions(-) diff --git a/isz2iso_gui.py b/isz2iso_gui.py index 206a60c..20d6e94 100644 --- a/isz2iso_gui.py +++ b/isz2iso_gui.py @@ -255,112 +255,175 @@ def extract_to(self, dest_iso): # ---- Original code ends here ---- - -# -------------------------------------------------------------- -# GUI -# -------------------------------------------------------------- +# ------------------------------------------------------------------ +# GUI application +# ------------------------------------------------------------------ class Application(tk.Tk): - def __init__(self): super().__init__() - self.title('ISZ → ISO converter') + self.title("ISZ → ISO converter") self.resizable(False, False) + self.eval("tk::PlaceWindow . center") - # center the dialog - self.geometry('400x200') - self.eval('tk::PlaceWindow . center') - - # vars - self.src_file = tk.StringVar() + # ------------------------------------------------------------------ + # Variables + # ------------------------------------------------------------------ + self.src_file = tk.StringVar() self.dest_file = tk.StringVar() - # layout + # ------------------------------------------------------------------ + # Layout + # ------------------------------------------------------------------ frm = ttk.Frame(self, padding=(10, 10, 10, 10)) frm.grid(row=0, column=0, sticky='nsew') frm.columnconfigure(1, weight=1) - ttk.Label(frm, text='Source ISZ file:').grid(row=0, column=0, sticky='w') - src_entry = ttk.Entry(frm, textvariable=self.src_file, width=40) - src_entry.grid(row=0, column=1, sticky='ew', padx=(0, 5)) - ttk.Button(frm, text='Browse…', command=self.browse_src).grid(row=0, column=2) - - ttk.Label(frm, text='Destination ISO:').grid(row=1, column=0, sticky='w') - dest_entry = ttk.Entry(frm, textvariable=self.dest_file, width=40) - dest_entry.grid(row=1, column=1, sticky='ew', padx=(0, 5)) - ttk.Button(frm, text='Browse…', command=self.browse_dest).grid(row=1, column=2) - - # Show a progress bar only if we want; for now just a simple status label + # Source – line 0 + ttk.Label(frm, text="Source ISZ file:").grid(row=0, column=0, sticky='w') + self.src_entry = ttk.Entry(frm, textvariable=self.src_file, width=40) + self.src_entry.grid(row=0, column=1, sticky='ew', padx=(0, 5)) + self.browse_src_btn = ttk.Button(frm, text='Browse…', command=self.browse_src) + self.browse_src_btn.grid(row=0, column=2) + + # Destination – line 1 + ttk.Label(frm, text="Destination ISO:").grid(row=1, column=0, sticky='w') + self.dest_entry = ttk.Entry(frm, textvariable=self.dest_file, width=40) + self.dest_entry.grid(row=1, column=1, sticky='ew', padx=(0, 5)) + self.browse_dest_btn = ttk.Button(frm, text='Browse…', command=self.browse_dest) + self.browse_dest_btn.grid(row=1, column=2) + + # Progress bar – line 2 + self.progress_bar = ttk.Progressbar(frm, orient='horizontal', + length=320, mode='determinate') + self.progress_bar.grid(row=2, columnspan=3, sticky='ew', pady=(5, 0)) + self.progress_bar['maximum'] = 0 + + # Status label – line 3 self.status_lbl = ttk.Label(frm, text='Ready', anchor='center') - self.status_lbl.grid(row=2, column=0, columnspan=3, pady=(10, 0), sticky='ew') + self.status_lbl.grid(row=3, column=0, columnspan=3, pady=(5, 0), sticky='ew') - # convert button - ttk.Button(frm, text='Convert → ISO', command=self.convert).grid(row=3, column=0, columnspan=3, pady=(10, 0)) + # Convert button – line 4 + self.convert_btn = ttk.Button(frm, text='Convert → ISO', command=self.convert) + self.convert_btn.grid(row=4, column=0, columnspan=3, pady=(10, 0)) - # -------------------------------------------------------------------- + # ------------------------------------------------------------------ + # File selectors + # ------------------------------------------------------------------ def browse_src(self): path = filedialog.askopenfilename( title='Select source ISZ file', - filetypes=[('ISZ files', '*.isz'), ('All files', '*.*')] - ) + filetypes=[('ISZ files', '*.isz'), ('All files', '*.*')]) if path: self.src_file.set(path) - # auto–guess destination if not already set if not self.dest_file.get(): - guess = os.path.splitext(path)[0] + '.iso' - self.dest_file.set(guess) + self.dest_file.set(os.path.splitext(path)[0] + '.iso') - # -------------------------------------------------------------------- def browse_dest(self): path = filedialog.asksaveasfilename( title='Select destination ISO', defaultextension='.iso', - filetypes=[('ISO files', '*.iso'), ('All files', '*.*')] - ) + filetypes=[('ISO files', '*.iso'), ('All files', '*.*')]) if path: self.dest_file.set(path) - # -------------------------------------------------------------------- + # ------------------------------------------------------------------ + # Conversion – runs in a background thread + # ------------------------------------------------------------------ def convert(self): src = self.src_file.get() - dst = self.dest_file.get() + dest = self.dest_file.get() if not src or not os.path.isfile(src): messagebox.showerror('Error', 'Please choose a valid .isz file.') return - if not dst: + if not dest: messagebox.showerror('Error', 'Please choose a destination file.') return - # Disable UI while converting - self.status_lbl.config(text='Converting…') - self.update_idletasks() + # Disable UI + initialise UI widgets + self.disable_ui() + self.progress_bar['value'] = 0 + self.progress_bar['maximum'] = 0 + self.status_lbl.config(text='Preparing…') - # Run conversion in a background thread so that the UI stays responsive - thread = threading.Thread(target=self._convert_worker, args=(src, dst), daemon=True) - thread.start() + # Launch worker thread + threading.Thread(target=self._convert_worker, + args=(src, dest), daemon=True).start() - # -------------------------------------------------------------------- - def _convert_worker(self, src, dst): + def _convert_worker(self, src, dest): try: isz = ISZ_File() isz.open_isz_file(src) - isz.extract_to(dst) + + total = len(isz.chunk_pointers) + # set the maximum once we have the size + self.after(0, lambda: self.set_progress_max(total)) + + # The conversion – write block by block, update progress + crc = 0 + with open(dest, 'wb') as outf: + for i in range(total): + data = isz.decompress_block(i) + outf.write(data) + crc = zlib.crc32(data, crc) & 0xffffffff + # UI‑safe progress update + self.after(0, lambda cur=i+1: self.update_progress(cur)) + isz.close_file() - self.status_lbl.config(text='Done!') - messagebox.showinfo('Success', f'Converted to:\n{dst}') - except Exception as exc: - self.status_lbl.config(text='Error') - messagebox.showerror('Error', f'Failed:\n{exc}') - finally: - # re‑enable the GUI - self.after(0, self._enable_ui) - def _enable_ui(self): - self.status_lbl.config(text='Ready') - # We could re‑enable buttons/entries if we had disabled them + # CRC check – identical to the original extract_to() + if (~crc) & 0xffffffff != isz.isz_header.checksum1: + raise Exception('CRC error during extraction') -# -------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------- -if __name__ == '__main__': + # Success → re‑enable UI & show a happy dialog + self.after(0, lambda: self.on_success(dest)) + except Exception as exc: + # Failure → re‑enable UI & show an error dialog + self.after(0, lambda: self.on_error(str(exc))) + + # ------------------------------------------------------------------ + # Helper methods that run in the *main* (UI) thread + # ------------------------------------------------------------------ + def set_progress_max(self, max_value): + self.progress_bar['maximum'] = max_value + self.progress_bar['value'] = 0 + + def update_progress(self, current): + self.progress_bar['value'] = current + max_val = self.progress_bar['maximum'] + self.status_lbl.config( + text=f'Converting {current}/{max_val} blocks ({current*100//max_val} %)') + + def on_success(self, dest): + self.enable_ui() + self.status_lbl.config(text='Done') + messagebox.showinfo('Success', f'Converted to:\n{dest}') + + def on_error(self, msg): + self.enable_ui() + self.status_lbl.config(text='Error') + messagebox.showerror('Error', msg) + + # ------------------------------------------------------------------ + # UI enable / disable helpers + # ------------------------------------------------------------------ + def disable_ui(self): + self.convert_btn['state'] = tk.DISABLED + self.src_entry['state'] = tk.DISABLED + self.dest_entry['state'] = tk.DISABLED + self.browse_src_btn['state'] = tk.DISABLED + self.browse_dest_btn['state'] = tk.DISABLED + + def enable_ui(self): + self.convert_btn['state'] = tk.NORMAL + self.src_entry['state'] = tk.NORMAL + self.dest_entry['state'] = tk.NORMAL + self.browse_src_btn['state'] = tk.NORMAL + self.browse_dest_btn['state'] = tk.NORMAL + +# ------------------------------------------------------------------ +# Run the application +# ------------------------------------------------------------------ +if __name__ == "__main__": app = Application() app.mainloop() + From e8e6c63dd0200da477671233339d56cd16169ec5 Mon Sep 17 00:00:00 2001 From: Nishant <46081095+ni6hant@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:32:06 +0530 Subject: [PATCH 3/5] Revise README for ISZtoISO project Updated README to reflect new project name and features, added acknowledgments and usage instructions. --- README.md | 168 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index c6115a4..7cf0388 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,143 @@ -isz-tool -======== +# ISZtoISO – Windows GUI converter (ISZ → ISO) -isz-tool is a command line utility to manipulate ISZ files (.isz), including -.isz to .iso conversion +> ⚙️ **AI‑assisted development** +> This project was assembled with the help of an AI language model (OpenAI’s ChatGPT). +> The core algorithm was originally written by [**Olivier Serres**](https://github.com/oserres) and a clean GUI wrapper + packaging was added by [**ni6hant**](https://github.com/ni6hant) for debugging and idea integration. +> [**ni6hant**](https://github.com/ni6hant) would like to point out at this point the deep disgust he feels in using AI to write this code for him knowing it will increase the prices of PC parts so much more that there will be a war. He takes responsibility for the rich vs. poor war that will happen in the future no matter which side he winds up then. -Overview --------- +--- -ISZ files (.isz) are compressed ISO files (also called ISO Zipped). They can be -opened by software such as Alcohol 120%, Daemon Tools and UltraISO [1]. +## 📦 What is this? -The main goal of this tool is to be able to convert ISZ files to ISO files. -At the time of writing isz-tool, I couldn't find any program able to handle -ISZ files under GNU/Linux. +ISZtoISO is a tiny Windows‑x64 **stand‑alone** program that converts a `.isz` (ISO‑packed) file into a normal `.iso` image. +It runs directly from the GitHub releases – you do **not** need Python or any external libraries installed on the target machine. -ISZ files support the following features : - - Decompression (using zlib or bzip2) - - Split files support (.isz, .i01, .i02, ...) - - CRC checksums of both compressed an uncompressed data +--- -ISZ tool is a small command line tool currently able to : - - Display informations about an ISZ file (uncompressed size, encryption - type...) - - Verify the file checksum - - Extract the file to an .iso file +## 🚀 Quick Start – For the “dumb user” -Currently not supported : - - Encryption - - Creation of an .isz file (before creating an .isz file, take into - consideration that a .iso.bz2 is much more portable) +1. **Download** the *latest release* from the [Releases page](https://github.com/ni6hant/isz2iso_gui/releases). + Find the file named `ISZtoISO.exe` inside the archive and extract it to a folder of your choice. -Usage ------ +2. **Run** `ISZtoISO.exe`. + A small window will appear with two file fields, a *Browse…* button on each side, a *Convert → ISO* button and a progress bar. -./isz-tool.py info file.isz - Print general information about file.isz +3. **Choose the source file** + - Click **Browse…** next to *Source ISZ file* and select the first `.isz` file of your multi‑part set (e.g. `image.isz`). + - The program will automatically look for the other parts (`image.part01.isz`, `image.part02a.isz`, …). + - If no other parts are needed the conversion will still work. -./isz-tool.py verify file.isz - Verify the CRC of file.isz +4. **Choose the destination** + - Click **Browse…** next to *Destination ISO* and pick the folder where you want the resulting `.iso` to be written. + - The default suggestion is the same name as the source file but with the extension changed to `.iso`. -./isz-tool.py verify --slow file.isz - Attempt to decompress and verify the CRC of file.isz +5. **Convert** + - Click **Convert → ISO**. + - The progress bar at the bottom will update as blocks are decompressed. + - When finished a dialog box will pop up saying **“Converted to: …”** -./isz-tool.py isz2iso file.isz file.iso - Convert file.isz to an ISO file +6. **Done!** + The `.iso` is now ready to be mounted, burned to DVD, or used in a virtual machine. -Dependencies ------------- +> **Common question** +> *Why do I get an “Error: Unable to read block” message?* +> The most frequent cause is a missing part of a multi‑file set. Make sure the whole series is in the same directory with the exact filenames the program expects. -Python 3.2 is required to run isz-tool +--- -Author ------- +## 🛠️ Building the executable – For developers -Olivier Serres - olivier.serres@gmail.com +If you want to build the `.exe` yourself (for example, after making changes or updating the GUI), follow these steps: -Links ------ -[1] http://en.wikipedia.org/wiki/UltraISO#ISZ_format +### 1️⃣ Prerequisites +| Item | Version | Why | +|------|---------|-----| +| Python | ≥ 3.10 | Needed to run [PyInstaller](https://www.pyinstaller.org) | +| pip | – | Package installer (comes with Python) | +| Git | – | Optional – to clone the repo | + +> NOTE: The project was written and tested on **Windows‑10 x64**. +> If you run on a different OS, you’ll need to cross‑compile or rebuild on a Windows machine. + +### 2️⃣ Get the source + +```bash +git clone https://github.com/ni6hant/isz2iso_gui.git +cd isz2iso_gui +``` + +> If you don’t have git, download the ZIP from the repository and extract it. + +### 3️⃣ Create a virtual environment (recommended) + +```bash +python -m venv .venv +.\.venv\Scripts\activate # on cmd.exe, use .venv\Scripts\activate.bat +# or: source .venv/bin/activate (on Unix) +``` + +### 4️⃣ Install build dependencies + +```bash +pip install --upgrade pip setuptools wheel +pip install pyinstaller +``` + +> **Tip:** If you already have PyInstaller installed globally, you still get a clean copy inside the virtualenv. + +### 5️⃣ Run PyInstaller + +```bash +pyinstaller --clean --onefile --noconsole --name ISZtoISO --icon isztosoft.ico isz2iso_gui.py +``` + +> - `--clean` – removes old build artifacts. +> - `--onefile` – bundles everything into a single `.exe`. +> - `--noconsole` – hides the console window. +> - `--icon` – optional – use a .ico file to give the executable an icon. +> - `isz2iso_gui.py` – entry point (the file we provided). + +> After a few seconds a **`dist/ISZtoISO.exe`** file will appear. +> You can copy it anywhere – it contains its own Python interpreter and all dependencies. + +### 6️⃣ Create a release + +1. Zip the `dist` folder (or just the `ISZtoISO.exe`). +2. Upload the archive to the *Releases* section of your GitHub repo. +3. Add a short release note (e.g. “Version 1.2.0 – progress bar added, minor bugfixes”). + +### 🧪 Quick test + +```bash +# On a clean Windows machine (no Python) +D:\temp\IszToIso\ISZtoISO.exe # just run it +``` + +The program should launch, show the GUI and perform conversions as described above. + +--- + +## 📄 License + +This project is licensed under the **GNU General Public License v3.0** (or any later version). +The original ISZ code (by Olivier Serres) is also licensed GPL‑3.0; see the file LICENSE or the header in the source for details. + +--- + +## 👥 Acknowledgements + +- **Olivier Serres** – original ISZ‑to‑ISO algorithm. +- **ni6hant** – debugging, adding the GUI, packaging, and the overall idea to make this a stand‑alone Windows app. +- **OpenAI** – the AI model that helped structure the code and documentation. + +--- + +## 📌 FAQ (quick references) + +| Question | Answer | +|----------|--------| +| **Do I need an internet connection to run the exe?** | No, all runtime dependencies are bundled. | +| **Will the exe work on PowerShell?** | Yes, simply double‑click or run from PowerShell – it behaves like any other native Windows program. | +| **Can I use it with a Multi‑Part ISZ set that uses a non‑standard naming scheme?** | Only the three following patterns are supported: `image.i01.isz`, `image.part01.isz`, `image.part001.isz`. | +| **What version of Python is included?** | The executable contains Python 3.10.12 (embedded). | From 395ee6f5e7d2fa897fbe8e4ba59c2f378c21a1d3 Mon Sep 17 00:00:00 2001 From: Nishant <46081095+ni6hant@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:32:18 +0530 Subject: [PATCH 4/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7cf0388..cfe9b38 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ > ⚙️ **AI‑assisted development** > This project was assembled with the help of an AI language model (OpenAI’s ChatGPT). > The core algorithm was originally written by [**Olivier Serres**](https://github.com/oserres) and a clean GUI wrapper + packaging was added by [**ni6hant**](https://github.com/ni6hant) for debugging and idea integration. +> > [**ni6hant**](https://github.com/ni6hant) would like to point out at this point the deep disgust he feels in using AI to write this code for him knowing it will increase the prices of PC parts so much more that there will be a war. He takes responsibility for the rich vs. poor war that will happen in the future no matter which side he winds up then. --- From 2badaf11f224e4ea7fee681dec9c2167ec9cb73d Mon Sep 17 00:00:00 2001 From: Nishant <46081095+ni6hant@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:33:33 +0530 Subject: [PATCH 5/5] Update README for user instructions and links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cfe9b38..14a2e78 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ It runs directly from the GitHub releases – you do **not** need Python or any --- -## 🚀 Quick Start – For the “dumb user” +## 🚀 For Users: -1. **Download** the *latest release* from the [Releases page](https://github.com/ni6hant/isz2iso_gui/releases). +1. **Download** the *latest release* from the [Releases page](https://github.com/ni6hant/isz-tool-windows/releases/). Find the file named `ISZtoISO.exe` inside the archive and extract it to a folder of your choice. 2. **Run** `ISZtoISO.exe`.