Restore compatibility with TOS distro (moved from isoparser to redseafs)

This commit is contained in:
minexew committed 2017-06-17 15:36:57 +02:00
1 parent 0e58717724
commit 7e46072c81
8 files changed
+598 -22

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
[submodule "isoparser"]
path = isoparser
url = https://github.com/barneygale/isoparser.git
+5 -7
View File
@@ -1,15 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python3
from __future__ import print_function
import hashlib import hashlib
import os import os
import subprocess import subprocess
import sys import sys
import urllib import urllib.request
ISO_URL = 'http://www.templeos.org/TempleOSCD.ISO' ISO_URL = 'http://www.templeos.org/files/TOS_Distro.ISO'
FILE_NAME = 'TempleOSCD.iso' FILE_NAME = 'TOS_Distro.ISO'
EXTRACTED_ISO_DIR = 'TempleOSCD' EXTRACTED_ISO_DIR = 'TempleOSCD'
force = (len(sys.argv) > 1 and sys.argv[1] == '-f') force = (len(sys.argv) > 1 and sys.argv[1] == '-f')
@@ -33,7 +31,7 @@ else:
print('-- Pre-hash:', pre_hash) print('-- Pre-hash:', pre_hash)
print('-- Grabbing', ISO_URL) print('-- Grabbing', ISO_URL)
urllib.urlretrieve(ISO_URL, FILE_NAME) urllib.request.urlretrieve(ISO_URL, FILE_NAME)
post_hash = hash_of_file(FILE_NAME) post_hash = hash_of_file(FILE_NAME)
print('-- Post-hash:', post_hash) print('-- Post-hash:', post_hash)
+18 -11
View File
@@ -1,17 +1,19 @@
#!/usr/bin/env python #!/usr/bin/env python3
import sys import sys
sys.path.append('isoparser') sys.path.append('redseafs')
import errno import errno
import isoparser from isoc import RedSea
import os import os
import subprocess import subprocess
ISO_FILE = sys.argv[1] ISO_FILE = sys.argv[1]
OUTPUT_DIR = sys.argv[2] OUTPUT_DIR = sys.argv[2]
iso = isoparser.parse(ISO_FILE) S_IFDIR = 0o040000 # directory
iso = RedSea(ISO_FILE)
def make_sure_path_exists(path): def make_sure_path_exists(path):
try: try:
@@ -20,15 +22,20 @@ def make_sure_path_exists(path):
if exception.errno != errno.EEXIST: if exception.errno != errno.EEXIST:
raise raise
def extract(node, path): def extract(iso_path, path):
make_sure_path_exists(path) make_sure_path_exists(path)
for entry in node.children:
full_path = os.path.join(path, entry.name)
if entry.is_directory: for entry in iso.readdir(iso_path if len(iso_path) else '/', -1):
extract(entry, full_path) if entry[0] == '.': continue
iso_full_path = iso_path + '/' + entry
full_path = os.path.join(path, entry)
stat = iso.getattr(iso_full_path)
if stat['st_mode'] & S_IFDIR:
extract(iso_full_path, full_path)
else: else:
open(full_path, 'wb').write(entry.content) open(full_path, 'wb').write(iso.read(iso_full_path, stat['st_size'], 0, -1))
def decompress_all_files_in(path): def decompress_all_files_in(path):
for item in os.listdir(path): for item in os.listdir(path):
@@ -42,7 +49,7 @@ def decompress_all_files_in(path):
os.remove(full_path) os.remove(full_path)
# Extract TempleOS disk tree # Extract TempleOS disk tree
extract(iso.root, OUTPUT_DIR) extract('', OUTPUT_DIR)
# Decompress compressed files # Decompress compressed files
decompress_all_files_in(OUTPUT_DIR) decompress_all_files_in(OUTPUT_DIR)
Submodule isoparser deleted from 6f18329bc1.
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
+36
View File
@@ -0,0 +1,36 @@
# redseafs
FUSE implementation of TempleOS RedSea file system
This is a proof-of-concept, it will probably get better. (Time zones are not handled correctly.)
Currently, you can use redseafs to create/modify/read RedSea ISO.C files on any system that supports FUSE.
# Commands
`isoc-mount [--rw] <filename.ISO.C> <mount_point>` will mount an ISO.C image on `mount_point`
Specify `--rw` to commit writes to ISO.C file, otherwise discarded on unmount.
Specify `--2k` to pad ISO.C file to multiple of 2048 bytes, for compatibility with VirtualBox virtual CD or physical disc ONLY
(2k padded ISO.C files will not mount with TempleOS `MountFile()`, you will get `ERROR: Not RedSea`)
If the ISO.C file does not exist, a blank filesystem will be created (and written on unmount if `--rw` specified.)
`fusermount -u <mount_point>` to unmount
# Installation
Clone the repo, move `isoc-mount` and `isoc.py` to `/usr/bin`, `chmod +x`.
On a Debian/Ubuntu system: `sudo apt install fuse; sudo apt install python-pip; sudo pip install fusepy`
NOTE: This will install fusepy globally, if that's not what you want... then you probably don't need instructions anyway :P
# Prerequisites
- FUSE
- pip install: fusepy
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python
import os, sys
pad = "0"
rw = ""
ctr = 0
while ctr < len(sys.argv):
if sys.argv[ctr].lower() == "--rw":
rw = "rw"
del(sys.argv[ctr])
ctr += 1
ctr = 0
while ctr < len(sys.argv):
if sys.argv[ctr].lower() == "--2k":
pad = "1"
del(sys.argv[ctr])
ctr += 1
if len(sys.argv) < 3:
print "Usage: " + sys.argv[0] + " [--rw] [--2k] <filename.ISO.C> <mount_point>"
print " --rw: commit writes to ISO.C file, otherwise discarded on unmount."
print " If the ISO.C file does not exist, a new one will be created."
print " --2k: Pad ISO.C to multiple of 2048 bytes "
print " (for VirtualBox or physical disk only) "
sys.exit()
os.system('"' + sys.argv[0][:sys.argv[0].rfind("/")+1] + 'isoc.py" "' + sys.argv[1] + '" "' + sys.argv[2] + '" "' + rw + '" "' + pad + '" &')
+488
View File
@@ -0,0 +1,488 @@
#!/usr/bin/env python
from __future__ import print_function, absolute_import, division
import base64
import binascii
import bz2
import calendar
import datetime
import logging
import os
import random
from collections import defaultdict
from errno import ENOENT
from stat import S_IFDIR, S_IFLNK, S_IFREG
from sys import argv, exit
from time import time
from time import localtime
from time import strftime
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn
if not hasattr(__builtins__, 'bytes'):
bytes = str
ISO9660_BOOT_BLK = bz2.decompress(base64.b64decode("""
QlpoOTFBWSZTWf7EvQUAAFv//fREJgRSAWAALyXeECYGQAQAQBkAABAgCACAEAAACLAAuSEpAk0bS
NNBpoNHqNNHqD1NDAGmho0YjIBoANDBFJINGjQAAAABo67j0bZyRUa5yTYIQ4kKTEIUwAXoAFqrSW
4rKEBSAhABlCEiGmEFTMYxS2moWhFsmA6oWhYpKyEGJZcHEC6HB+P3rMF7qCe92GE9TJlRStvcUio
nnIx8jGJrKA+I80GdJI5JNH6AqxFBNVCiJ+/i7kinChIf2JegoA==
"""))
epoch = datetime.datetime.utcfromtimestamp(0)
CDATE_YEAR_DAYS_INT = 36524225
CDIR_FILENAME_LEN = 38
RS_ATTR_READ_ONLY = 0x01 #R
RS_ATTR_HIDDEN = 0x02 #H
RS_ATTR_SYSTEM = 0x04 #S
RS_ATTR_VOL_ID = 0x08 #V
RS_ATTR_DIR = 0x10 #D
RS_ATTR_ARCHIVE = 0x20 #A
RS_ATTR_DELETED = 0x100 #X
RS_ATTR_RESIDENT = 0x200 #T
RS_ATTR_COMPRESSED = 0x400 #Z
RS_ATTR_CONTIGUOUS = 0x800 #C
RS_ATTR_FIXED = 0x1000 #F
RS_BLK_SIZE = 512
RS_DRV_OFFSET = 0xB000
RS_ROOT_CLUS = 0x5A
mon_start_days1=[
0,31,59,90,120,151,181,212,243,273,304,334]
mon_start_days2=[
0,31,60,91,121,152,182,213,244,274,305,335]
def roundup(x): return x if x % 2048 == 0 else x + 2048 - x % 2048
def write_iso_c(self, iso_c_file, pad):
dirs = []
dir_entries = {}
# Create dict for each directory
for i in self.files:
if self.files[i]['st_mode'] & 40000 == 64:
dir_entries[i] = {}
dir_entries[i]['clus'] = 0
dir_entries[i]['files'] = []
dir_entries[i]['files'].append({'filename':'.','clus':0,'st_size':0x400,'st_mode':64,'st_mtime':time()})
dir_entries[i]['files'].append({'filename':'..','clus':0,'st_size':0x00,'st_mode':64,'st_mtime':time()})
dirs.append(i)
# Place files in corresponding dicts
for d in sorted(dirs, reverse=True):
for i in self.files:
if i.find((d+"/").replace("//","/")) != -1 and i != d:
if 'filename' not in self.files[i]:
self.files[i]['filename'] = i.split('/')[len(i.split('/'))-1]
self.files[i]['clus'] = 0
dir_entries[d]['files'].append(self.files[i])
# Calculate CDirEntry clusters
de_tbl_size = 0
de_clus_ctr = RS_ROOT_CLUS
for d in sorted(dir_entries):
ct_entries = 1+len(dir_entries[d]['files'])
ct_size = RS_BLK_SIZE * int((1+(ct_entries*64) / RS_BLK_SIZE))
de_tbl_size += ct_size
dir_entries[d]['clus'] = de_clus_ctr
de_clus_ctr += int((ct_size / RS_BLK_SIZE))
# Link nested CDirEntries
for d in dirs:
de_filename = d[d.rfind("/")+1:]
if len(d.split("/")) > 2:
de_parent = d[:d.rfind("/")]
else:
de_parent = "/"
de_idx = 0
for de in dir_entries[de_parent]['files']:
if de['filename'] == de_filename:
dir_entries[de_parent]['files'][de_idx]['clus'] = dir_entries[(de_parent + "/" + de_filename).replace("//","/")]['clus']
de_idx += 1
# Link dotted dirs ".", ".."
for de_parent in dir_entries:
de_idx = 0
for de in dir_entries[de_parent]['files']:
if de['filename'] == ".":
dir_entries[de_parent]['files'][de_idx]['clus'] = dir_entries[de_parent]['clus']
# Calculate length of this directory
dir_entries[de_parent]['files'][de_idx]['st_size'] = RS_BLK_SIZE * (1+int(64*(len(dir_entries[de_parent]['files'])) / RS_BLK_SIZE))
if de['filename'] == "..":
dir_entries[de_parent]['files'][de_idx]['clus'] = dir_entries[("/" + de_parent[:de_parent.rfind("/")]).replace("//","/")]['clus']
de_idx += 1
# Update size for subdir entries
for de_parent in dir_entries:
de_idx = 0
for de in dir_entries[de_parent]['files']:
if de['st_mode'] & 40000 == 64 and de['filename'] != "." and de['filename'] != "..":
for sde in dir_entries[(de_parent+"/"+de['filename']).replace("//","/")]['files']:
if sde['filename'] == ".":
dir_entries[de_parent]['files'][de_idx]['st_size'] = sde['st_size']
de_idx += 1
# Calculate cluster offset for files
for de_parent in dir_entries:
de_idx = 0
for de in dir_entries[de_parent]['files']:
if de['clus'] == 0:
dir_entries[de_parent]['files'][de_idx]['clus'] = de_clus_ctr
ct_size = RS_BLK_SIZE * int((1+(de['st_size']) / RS_BLK_SIZE))
de_clus_ctr += int((ct_size / RS_BLK_SIZE))
de_idx += 1
file = open(iso_c_file, "wb")
# Write ISO9660 boot block
file.seek(0)
file.write(ISO9660_BOOT_BLK)
# Write CDirEntries
for d in sorted(dirs):
de_offset = int(dir_entries[d]['clus']*RS_BLK_SIZE)
for f in sorted(dir_entries[d]['files'], key=lambda k: k['filename']):
file.seek(de_offset)
if f['st_mode'] & 40000 == 64:
# Directory
file.write('\x10')
else:
# File
file.write('\x20')
if f['filename'][-2:] == ".Z":
file.write('\x0c')
else:
file.write('\x08')
file.write(f['filename'].ljust(CDIR_FILENAME_LEN,'\x00'))
# Cluster
file.seek(de_offset+0x28)
clus_bige = binascii.unhexlify(format(int(f['clus']), '016X'))
clus_bige_ctr = 0
while clus_bige_ctr < 8:
file.write(chr(ord(clus_bige[7-clus_bige_ctr])))
clus_bige_ctr += 1
file.seek(de_offset+0x30)
# Size
size_bige = binascii.unhexlify(format(int(f['st_size']), '016X'))
size_bige_ctr = 0
while size_bige_ctr < 8:
file.write(chr(ord(size_bige[7-size_bige_ctr])))
size_bige_ctr += 1
# DateTime
file.seek(de_offset+0x38)
dt_bige = Unix2CDate(localtime(f['st_mtime']+1))
dt_bige_ctr = 0
while dt_bige_ctr < 8:
file.write(chr(ord(dt_bige[7-dt_bige_ctr])))
dt_bige_ctr += 1
de_offset += 64
# Write files
for d in sorted(dirs):
de_offset = int(dir_entries[d]['clus']*RS_BLK_SIZE)
for f in sorted(dir_entries[d]['files'], key=lambda k: k['filename']):
file.seek(de_offset)
if f['st_mode'] & 40000 != 64:
file.seek(int(f['clus']*RS_BLK_SIZE))
file.write(self.data[(d+'/'+f['filename']).replace("//","/")])
# If --2k, Set counter to byte multiple 2048
if int(pad) == 1:
de_clus_ctr=roundup(de_clus_ctr)
# Write to EOF
file.seek(RS_DRV_OFFSET+int(de_clus_ctr*RS_BLK_SIZE)-1)
file.write(chr(0))
# Write boot sector
bs = [0x00]*RS_BLK_SIZE
bs[0x003] = 0x88 # signature
bs[0x008] = 0x58 # cdrom offset
# -- cluster count
cl_sz = binascii.unhexlify(format(int(de_clus_ctr), '016X'))
cl_sz_ctr = 0
while cl_sz_ctr < 8:
bs[0x10+cl_sz_ctr] = ord(cl_sz[7-cl_sz_ctr])
cl_sz_ctr += 1
bs[0x018] = 0x5A # root entry cluster [90]
bs[0x020] = 0x01 # bitmap_sects (unused for ISO.C?)
# -- unique id
id_ctr = 0
while id_ctr<8:
bs[0x028+id_ctr] = int(random.random()*256) % 256
id_ctr += 1
bs[0x1FE] = 0x55 # signature
bs[0x1FF] = 0xAA # signature
file.seek(RS_DRV_OFFSET)
for byte in bs:
file.write(chr(byte))
file.close()
def CDate2Unix(c_date, c_time):
year=int((c_date+1)*100000/CDATE_YEAR_DAYS_INT)
c_year = int(year)
i=YearStartDate(c_year)
while i > c_date:
c_year -= 1
i=YearStartDate(c_year)
c_date -= i
c_date = int(c_date)
if calendar.isleap(year) and c_date>29:
c_date += 1
k=(625*15*15*3*c_time) >> 21
min = int(k/100/100/60 % 60)
hour = int(k/100/100/60/60)
# lol, timezones
i_dst = 0
if strftime("%Z") == "EDT":
chk_date = datetime.datetime(year, 1, 1, hour, min, 0)
if is_dst(chk_date):
i_dst = 1
return (datetime.datetime(year, 1, 1, hour, min, 0)-epoch+datetime.timedelta(days=int(c_date), hours=i_dst+(-int(strftime("%z"))/100))).total_seconds()
def is_dst(dt):
if dt.year < 2007:
# huehuehue
return False
dst_start = datetime.datetime(dt.year, 3, 8, 2, 0)
dst_start += datetime.timedelta(6 - dst_start.weekday())
dst_end = datetime.datetime(dt.year, 11, 1, 2, 0)
dst_end += datetime.timedelta(6 - dst_end.weekday())
return dst_start <= dt < dst_end
def YearStartDate(year):
y1=year-1
yd4000=y1/4000
yd400=y1/400
yd100=y1/100
yd4=y1/4;
return year*365+yd4-yd100+yd400-yd4000
def Unix2CDate(dt):
il=YearStartDate(dt.tm_year)
i2=YearStartDate(dt.tm_year+1)
if (i2-il==365):
il += mon_start_days1[dt.tm_mon-1]
else:
il += mon_start_days2[dt.tm_mon-1]
_date = il + (dt.tm_mday-1);
_time=(100*(100*(dt.tm_sec+60*(dt.tm_min+60*dt.tm_hour)))<<21)/(15*15*3*625)
return binascii.unhexlify(format(int(_date), '08X')) + binascii.unhexlify(format(int(_time), '08X'))
class RedSea(LoggingMixIn, Operations):
def __init__(self, iso_c_file):
self.files = {}
self.data = defaultdict(bytes)
self.fd = 0
self.modified = False
now = time()
self.files['/'] = dict(st_mode=(S_IFDIR | 0o755), st_ctime=now,
st_mtime=now, st_atime=now, st_nlink=2)
if os.path.exists(iso_c_file):
f = open(iso_c_file, "rb").read()
blkdev_offset = RS_DRV_OFFSET
de_list = { '': blkdev_offset + int((1+int(f[blkdev_offset+0x20]))*RS_BLK_SIZE) }
else:
de_list = {}
# If iso_c_file exists, read dirs/files
while len(de_list) > 0 and os.path.exists(iso_c_file):
dir = next(iter(de_list.keys())) + '/'
ofs = de_list[next(iter(de_list.keys()))]
del(de_list[next(iter(de_list.keys()))])
# Go to first CDirEntry, skipping ".", ".."
pos = 128
while f[ofs+pos+2] != 0x00:
ctr = int(ofs+pos)
de_attrs = u16(f[ctr:ctr+2])
ctr += 2
de_filename = f[ctr:ctr+CDIR_FILENAME_LEN].replace(b'\x00',b'').decode('ascii')
ctr += CDIR_FILENAME_LEN
de_clus = i64(f[ctr:ctr+8])
ctr += 8
de_size = i64(f[ctr:ctr+8])
ctr += 8
de_time = i32(f[ctr:ctr+4])
de_date = i32(f[ctr+4:ctr+8])
ctr += 8
de_mode = S_IFREG | 0o755
if de_attrs & RS_ATTR_DIR:
de_mode = S_IFDIR | 0o755
de_list[dir + de_filename] = ((int(de_clus))*RS_BLK_SIZE)
self.files[dir + de_filename] = dict(st_mode=de_mode, st_ctime=CDate2Unix(de_date, de_time),
st_mtime=CDate2Unix(de_date, de_time), st_atime=now, st_nlink=2, st_size=de_size)
if not de_attrs & RS_ATTR_DIR:
self.data[dir + de_filename] = f[RS_BLK_SIZE*de_clus:(RS_BLK_SIZE*de_clus)+de_size]
pos += 64
if os.path.exists(iso_c_file):
del(f)
def chmod(self, path, mode):
self.files[path]['st_mode'] &= 0o770000
self.files[path]['st_mode'] |= mode
return 0
def chown(self, path, uid, gid):
self.files[path]['st_uid'] = uid
self.files[path]['st_gid'] = gid
def create(self, path, mode):
self.files[path] = dict(st_mode=(S_IFREG | mode), st_nlink=1,
st_size=0, st_ctime=time(), st_mtime=time(),
st_atime=time())
self.fd += 1
self.modified = True
return self.fd
def getattr(self, path, fh=None):
if path not in self.files:
raise FuseOSError(ENOENT)
return self.files[path]
def getxattr(self, path, name, position=0):
attrs = self.files[path].get('attrs', {})
try:
return attrs[name]
except KeyError:
return '' # Should return ENOATTR
def listxattr(self, path):
attrs = self.files[path].get('attrs', {})
return attrs.keys()
def mkdir(self, path, mode):
self.files[path] = dict(st_mode=(S_IFDIR | mode), st_nlink=2,
st_size=0, st_ctime=time(), st_mtime=time(),
st_atime=time())
self.files['/']['st_nlink'] += 1
self.modified = True
def open(self, path, flags):
self.fd += 1
return self.fd
def read(self, path, size, offset, fh):
return self.data[path][offset:offset + size]
def readdir(self, path, fh):
dir = [ '.', '..' ]
if path == '/':
for f in self.files:
if f.rfind('/') == 0 and f != '/':
dir.append(str(f)[1:])
else:
for f in self.files:
if f.startswith(path):
if f[len(path):].rfind('/') == 0 and f[len(path):] != '/':
dir.append(str(f[len(path):])[1:])
return dir
def readlink(self, path):
return self.data[path]
def removexattr(self, path, name):
attrs = self.files[path].get('attrs', {})
try:
del attrs[name]
except KeyError:
pass # Should return ENOATTR
def rename(self, old, new):
self.files[new] = self.files.pop(old)
self.modified = True
def rmdir(self, path):
self.files.pop(path)
self.files['/']['st_nlink'] -= 1
self.modified = True
def setxattr(self, path, name, value, options, position=0):
# Ignore options
attrs = self.files[path].setdefault('attrs', {})
attrs[name] = value
def statfs(self, path):
size = 256
return dict(f_bsize=RS_BLK_SIZE, f_blocks=((size*1024)*2), f_bavail=((size*1024)*2))
def symlink(self, target, source):
self.files[target] = dict(st_mode=(S_IFLNK | 0o777), st_nlink=1,
st_size=len(source))
self.data[target] = source
def truncate(self, path, length, fh=None):
self.data[path] = self.data[path][:length]
self.files[path]['st_size'] = length
def unlink(self, path):
self.files.pop(path)
self.modified = True
def utimens(self, path, times=None):
now = time()
atime, mtime = times if times else (now, now)
self.files[path]['st_atime'] = atime
self.files[path]['st_mtime'] = mtime
def write(self, path, data, offset, fh):
self.data[path] = self.data[path][:offset] + data
self.files[path]['st_size'] = len(self.data[path])
self.modified = True
return len(data)
def destroy(self, d):
if self.modified and argv[3] == "rw":
write_iso_c(self, argv[1], argv[4])
def u16(s):
u = s[0]
u += (256**1)*s[1]
return u
def i32(s):
# Not handled correctly (yet) but idgaf
c = 1
u = s[0]
while c < 4:
u += (256**c)*s[c]
c += 1
return u
def i64(s):
# Not handled correctly (yet) but idgaf
c = 1
u = s[0]
while c < 8:
u += (256**c)*s[c]
c += 1
return u
if __name__ == '__main__':
fuse = FUSE(RedSea(argv[1]), argv[2], foreground=True)