├── .gitignore ├── LICENSE ├── README.mkd ├── TODO └── ciso.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.pyo 4 | *.swp 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 David O'Rourke 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | ciso 2 | ---- 3 | 4 | Based on ciso from https://github.com/jamie/ciso 5 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Massive tidy up. 2 | Threading for compression (threading/Queue) 3 | -------------------------------------------------------------------------------- /ciso.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import struct 5 | import sys 6 | import zlib 7 | 8 | CISO_MAGIC = 0x4F534943 # CISO 9 | CISO_HEADER_SIZE = 0x18 # 24 10 | CISO_BLOCK_SIZE = 0x800 # 2048 11 | CISO_HEADER_FMT = '> i & 0xFF) for i in (0,8,16,24)]), 43 | 'header_size': header_size, 44 | 'total_bytes': total_bytes, 45 | 'block_size': block_size, 46 | 'ver': ver, 47 | 'align': align, 48 | 'total_blocks': int(total_bytes / block_size), 49 | } 50 | ciso['index_size'] = (ciso['total_blocks'] + 1) * 4 51 | else: 52 | raise Exception("Not a CISO file.") 53 | return ciso 54 | 55 | def update_progress(progress): 56 | barLength = console_width - len("Progress: 100% []") - 1 57 | block = int(round(barLength*progress)) + 1 58 | text = "\rProgress: [{blocks}] {percent:.0f}%".format( 59 | blocks="#" * block + "-" * (barLength - block), 60 | percent=progress * 100) 61 | sys.stdout.write(text) 62 | sys.stdout.flush() 63 | 64 | def decompress_cso(infile, outfile): 65 | with open(outfile, 'wb') as fout: 66 | with open(infile, 'rb') as fin: 67 | data = seek_and_read(fin, 0, CISO_HEADER_SIZE) 68 | header_data = struct.unpack(CISO_HEADER_FMT, data) 69 | ciso = parse_header_info(header_data) 70 | 71 | # Print some info before we start 72 | print("Decompressing '{}' to '{}'".format( 73 | infile, outfile)) 74 | for k, v in ciso.items(): 75 | print("{}: {}".format(k, v)) 76 | 77 | # Get the block index 78 | block_index = [struct.unpack(" percent_cnt: 118 | update_progress((block / (ciso['total_blocks'] + 1))) 119 | percent_cnt = percent 120 | # close infile 121 | # close outfile 122 | return True 123 | 124 | def check_file_size(f): 125 | f.seek(0, os.SEEK_END) 126 | file_size = f.tell() 127 | ciso = { 128 | 'magic': CISO_MAGIC, 129 | 'ver': 1, 130 | 'block_size': CISO_BLOCK_SIZE, 131 | 'total_bytes': file_size, 132 | 'total_blocks': int(file_size / CISO_BLOCK_SIZE), 133 | 'align': 0, 134 | } 135 | f.seek(0, os.SEEK_SET) 136 | return ciso 137 | 138 | def write_cso_header(f, ciso): 139 | f.write(struct.pack(CISO_HEADER_FMT, 140 | ciso['magic'], 141 | CISO_HEADER_SIZE, 142 | ciso['total_bytes'], 143 | ciso['block_size'], 144 | ciso['ver'], 145 | ciso['align'] 146 | )) 147 | 148 | def write_block_index(f, block_index): 149 | for index, block in enumerate(block_index): 150 | try: 151 | f.write(struct.pack('> ciso['align'] 196 | 197 | # Read raw data 198 | raw_data = fin.read(ciso['block_size']) 199 | raw_data_size = len(raw_data) 200 | 201 | # Compress block 202 | # Compressed data will have the gzip header on it, we strip that. 203 | compressed_data = zlib.compress(raw_data, compression_level)[2:] 204 | compressed_size = len(compressed_data) 205 | 206 | if compressed_size >= raw_data_size: 207 | writable_data = raw_data 208 | # Plain block marker 209 | block_index[block] |= 0x80000000 210 | # Next index 211 | write_pos += raw_data_size 212 | else: 213 | writable_data = compressed_data 214 | # Next index 215 | write_pos += compressed_size 216 | 217 | # Write data 218 | fout.write(writable_data) 219 | 220 | # Progress bar 221 | percent = int(round((block / (ciso['total_blocks'] + 1)) * 100)) 222 | if percent > percent_cnt: 223 | update_progress((block / (ciso['total_blocks'] + 1))) 224 | percent_cnt = percent 225 | 226 | # end for block 227 | # last position (total size) 228 | block_index[block] = write_pos >> ciso['align'] 229 | 230 | # write header and index block 231 | print("Writing block index") 232 | fout.seek(CISO_HEADER_SIZE, os.SEEK_SET) 233 | write_block_index(fout, block_index) 234 | # end open(infile) 235 | 236 | def main(argv): 237 | compression_level = int(argv[1]) 238 | infile = argv[2] 239 | outfile = argv[3] 240 | if compression_level: 241 | compress_iso(infile, outfile, compression_level) 242 | else: 243 | decompress_cso(infile, outfile) 244 | 245 | if __name__ == '__main__': 246 | sys.exit(main(sys.argv)) 247 | --------------------------------------------------------------------------------