3 di_iso9660.c -- an ISO9660 DVD devoptab library for the Wii
5 Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
7 This software is provided 'as-is', without any express or implied warranty.
8 In no event will the authors be held liable for any damages arising from
9 the use of this software.
11 Permission is granted to anyone to use this software for any purpose,
12 including commercial applications, and to alter it and redistribute it
13 freely, subject to the following restrictions:
15 1.The origin of this software must not be misrepresented; you must not
16 claim that you wrote the original software. If you use this software in a
17 product, an acknowledgment in the product documentation would be
18 appreciated but is not required.
20 2.Altered source versions must be plainly marked as such, and must not be
21 misrepresented as being the original software.
23 3.This notice may not be removed or altered from any source distribution.
32 #include <sys/iosupport.h>
35 #include <ogc/lwp_watchdog.h>
36 #include <ogc/mutex.h>
39 #include <di/di_iso9660.h>
41 #define DEVICE_NAME "dvd"
44 #define DIR_SEPARATOR '/'
45 #define SECTOR_SIZE 0x800
46 #define BUFFER_SIZE 0x8000
53 char name
[ISO_MAXPATHLEN
];
54 } __attribute__((packed
)) PATHTABLE_ENTRY
;
56 typedef struct PATH_ENTRY_STRUCT
{
57 PATHTABLE_ENTRY table_entry
;
60 struct PATH_ENTRY_STRUCT
*children
;
63 typedef struct DIR_ENTRY_STRUCT
{
64 char name
[ISO_MAXPATHLEN
];
69 PATH_ENTRY
*path_entry
;
70 struct DIR_ENTRY_STRUCT
*children
;
85 static mutex_t _mutex
= 0;
86 static u8 read_buffer
[BUFFER_SIZE
] __attribute__((aligned(32)));
87 static u8 cluster_buffer
[BUFFER_SIZE
] __attribute__((aligned(32)));
88 static u32 cache_start
= 0;
89 static u32 cache_sectors
= 0;
91 static PATH_ENTRY
*root
= NULL
;
92 static PATH_ENTRY
*current
= NULL
;
93 static bool unicode
= false;
94 static u64 last_access
= 0;
95 static s32 dotab_device
= -1;
97 static bool is_dir(DIR_ENTRY
*entry
) {
98 return entry
->flags
& FLAG_DIR
;
101 #define OFFSET_EXTENDED 1
102 #define OFFSET_SECTOR 6
103 #define OFFSET_SIZE 14
104 #define OFFSET_FLAGS 25
105 #define OFFSET_NAMELEN 32
106 #define OFFSET_NAME 33
108 // lock mutex before calling
109 static int _read(void *ptr
, u64 offset
, size_t len
) {
110 u32 sector
= offset
/ SECTOR_SIZE
;
111 u32 end_sector
= (offset
+ len
- 1) / SECTOR_SIZE
;
112 u32 sectors
= MIN(BUFFER_SIZE
/ SECTOR_SIZE
, end_sector
- sector
+ 1);
113 u32 sector_offset
= offset
% SECTOR_SIZE
;
114 len
= MIN(BUFFER_SIZE
- sector_offset
, len
);
116 if (cache_sectors
&& sector
>= cache_start
&& (sector
+ sectors
) <= (cache_start
+ cache_sectors
)) {
117 memcpy(ptr
, read_buffer
+ (sector
- cache_start
) * SECTOR_SIZE
+ sector_offset
, len
);
121 if (DI_ReadDVD(read_buffer
, BUFFER_SIZE
/ SECTOR_SIZE
, sector
)) {
122 last_access
= gettime();
127 last_access
= gettime();
128 cache_start
= sector
;
129 cache_sectors
= BUFFER_SIZE
/ SECTOR_SIZE
;
130 memcpy(ptr
, read_buffer
+ sector_offset
, len
);
134 static int read_entry(DIR_ENTRY
*entry
, u8
*buf
) {
135 u8 extended_sectors
= buf
[OFFSET_EXTENDED
];
136 u32 sector
= *(u32
*)(buf
+ OFFSET_SECTOR
) + extended_sectors
;
137 u32 size
= *(u32
*)(buf
+ OFFSET_SIZE
);
138 u8 flags
= buf
[OFFSET_FLAGS
];
139 u8 namelen
= buf
[OFFSET_NAMELEN
];
141 if (namelen
== 1 && buf
[OFFSET_NAME
] == 1) {
143 } else if (namelen
== 1 && !buf
[OFFSET_NAME
]) {
144 entry
->sector
= sector
;
146 entry
->flags
= flags
;
148 DIR_ENTRY
*newChildren
= realloc(entry
->children
, sizeof(DIR_ENTRY
) * (entry
->fileCount
+ 1));
149 if (!newChildren
) return -1;
150 bzero(newChildren
+ entry
->fileCount
, sizeof(DIR_ENTRY
));
151 entry
->children
= newChildren
;
152 DIR_ENTRY
*child
= &entry
->children
[entry
->fileCount
++];
153 child
->sector
= sector
;
155 child
->flags
= flags
;
156 char *name
= child
->name
;
159 for (i
= 0; i
< (namelen
/ 2); i
++) name
[i
] = buf
[OFFSET_NAME
+ i
* 2 + 1];
163 memcpy(name
, buf
+ OFFSET_NAME
, namelen
);
164 name
[namelen
] = '\x00';
166 if (!(flags
& FLAG_DIR
) && namelen
>= 2 && name
[namelen
- 2] == ';') name
[namelen
- 2] = '\x00';
172 static bool read_directory(DIR_ENTRY
*dir_entry
, PATH_ENTRY
*path_entry
) {
173 u32 sector
= path_entry
->table_entry
.sector
;
175 u32 sector_offset
= 0;
177 LWP_MutexLock(_mutex
);
179 if (_read(cluster_buffer
, (u64
)sector
* SECTOR_SIZE
+ sector_offset
, (SECTOR_SIZE
- sector_offset
)) != (SECTOR_SIZE
- sector_offset
)) {
180 LWP_MutexUnlock(_mutex
);
184 int offset
= read_entry(dir_entry
, cluster_buffer
);
186 LWP_MutexUnlock(_mutex
);
191 remaining
= dir_entry
->size
;
192 dir_entry
->path_entry
= path_entry
;
195 sector_offset
+= offset
;
196 if (sector_offset
>= SECTOR_SIZE
|| !cluster_buffer
[offset
]) {
197 remaining
-= SECTOR_SIZE
;
201 } while (remaining
> 0);
202 LWP_MutexUnlock(_mutex
);
207 static bool path_entry_from_path(PATH_ENTRY
*path_entry
, const char *path
) {
209 bool notFound
= false;
210 const char *pathPosition
= path
;
211 const char *pathEnd
= strchr(path
, '\0');
212 PATH_ENTRY
*entry
= root
;
213 while (pathPosition
[0] == DIR_SEPARATOR
) pathPosition
++;
214 if (pathPosition
>= pathEnd
) found
= true;
215 PATH_ENTRY
*dir
= entry
;
216 while (!found
&& !notFound
) {
217 const char *nextPathPosition
= strchr(pathPosition
, DIR_SEPARATOR
);
218 size_t dirnameLength
;
219 if (nextPathPosition
!= NULL
) dirnameLength
= nextPathPosition
- pathPosition
;
220 else dirnameLength
= strlen(pathPosition
);
221 if (dirnameLength
>= ISO_MAXPATHLEN
) return false;
224 while (childIndex
< dir
->childCount
&& !found
&& !notFound
) {
225 entry
= &dir
->children
[childIndex
];
226 if (dirnameLength
== strnlen(entry
->table_entry
.name
, ISO_MAXPATHLEN
- 1) && !strncasecmp(pathPosition
, entry
->table_entry
.name
, dirnameLength
)) found
= true;
227 if (!found
) childIndex
++;
230 if (childIndex
>= dir
->childCount
) {
233 } else if (!nextPathPosition
|| nextPathPosition
>= pathEnd
) {
237 pathPosition
= nextPathPosition
;
238 while (pathPosition
[0] == DIR_SEPARATOR
) pathPosition
++;
239 if (pathPosition
>= pathEnd
) found
= true;
244 if (found
) memcpy(path_entry
, entry
, sizeof(PATH_ENTRY
));
248 static bool find_in_directory(DIR_ENTRY
*dir_entry
, PATH_ENTRY
*parent
, const char *base
) {
249 u32 nl
= strlen(base
);
251 // there will be no basename if we are looking for root
253 return read_directory(dir_entry
, parent
);
256 // check directories i know about first
258 for (childIndex
= 0; childIndex
< parent
->childCount
; childIndex
++) {
259 PATH_ENTRY
*child
= parent
->children
+ childIndex
;
260 if (nl
== strnlen(child
->table_entry
.name
, ISO_MAXPATHLEN
- 1) && !strncasecmp(base
, child
->table_entry
.name
, nl
)) {
261 // found the thing we're after and it is a directory
262 // read it into dir_entry, and return true
263 return read_directory(dir_entry
, child
);
267 // read ourselves into a DIR_ENTRY, look into children for matching file
268 if (!read_directory(dir_entry
, parent
)) return false;
269 for (childIndex
= 0; childIndex
< dir_entry
->fileCount
; childIndex
++) {
270 DIR_ENTRY
*child
= dir_entry
->children
+ childIndex
;
271 if (nl
== strnlen(child
->name
, ISO_MAXPATHLEN
- 1) && !strncasecmp(base
, child
->name
, nl
)) {
272 // found the thing we're after and it is a file
273 // stick it in dir_entry, and return true
274 memcpy(dir_entry
, child
, sizeof(DIR_ENTRY
));
282 // result gets alloced, dont forget to free it
283 static char *dirname(char *path
) {
285 char *result
= strdup(path
);
287 j
= strlen(result
) - 1;
292 for (i
= j
; i
>= 0; i
--)
293 if (result
[i
] == DIR_SEPARATOR
) {
302 static char *basename(char *path
) {
304 for (i
= strlen(path
) - 1; i
>= 0; i
--) {
305 if (path
[i
] == DIR_SEPARATOR
) {
312 static bool invalid_drive_specifier(const char *path
) {
313 if (strchr(path
, ':') == NULL
) return false;
314 int namelen
= strlen(DEVICE_NAME
);
315 if (!strncmp(DEVICE_NAME
, path
, namelen
) && path
[namelen
] == ':') return false;
319 static bool entry_from_path(DIR_ENTRY
*dir_entry
, const char *const_path
) {
320 bzero(dir_entry
, sizeof(DIR_ENTRY
));
322 if (invalid_drive_specifier(const_path
)) return false;
324 // get rid of drive specifier
325 if (strchr(const_path
, ':') != NULL
) const_path
= strchr(const_path
, ':') + 1;
327 char path
[strlen(const_path
) + 1];
328 strcpy(path
, const_path
);
330 // strip trailing slashes except for root
331 u32 len
= strlen(path
);
332 while (len
> 1 && path
[len
- 1] == DIR_SEPARATOR
) {
333 path
[--len
] = '\x00';
337 char *dir
= dirname(path
);
338 char *base
= basename(path
);
340 PATH_ENTRY parent_entry
;
341 if (!path_entry_from_path(&parent_entry
, dir
)) {
347 bool found
= find_in_directory(dir_entry
, &parent_entry
, base
);
348 if (!found
&& dir_entry
->children
) free(dir_entry
->children
);
352 static int _ISO9660_open_r(struct _reent
*r
, void *fileStruct
, const char *path
, int flags
, int mode
) {
353 FILE_STRUCT
*file
= (FILE_STRUCT
*)fileStruct
;
355 if (!entry_from_path(&entry
, path
)) {
358 } else if (is_dir(&entry
)) {
359 if (entry
.children
) free(entry
.children
);
364 memcpy(&file
->entry
, &entry
, sizeof(DIR_ENTRY
));
371 static int _ISO9660_close_r(struct _reent
*r
, int fd
) {
372 FILE_STRUCT
*file
= (FILE_STRUCT
*)fd
;
381 static int _ISO9660_read_r(struct _reent
*r
, int fd
, char *ptr
, size_t len
) {
382 FILE_STRUCT
*file
= (FILE_STRUCT
*)fd
;
387 if (file
->offset
>= file
->entry
.size
) {
388 r
->_errno
= EOVERFLOW
;
391 if (len
+ file
->offset
> file
->entry
.size
) {
392 r
->_errno
= EOVERFLOW
;
393 len
= file
->entry
.size
- file
->offset
;
399 u64 offset
= file
->entry
.sector
* SECTOR_SIZE
+ file
->offset
;
401 LWP_MutexLock(_mutex
);
402 if ((len
= _read(ptr
, offset
, len
)) < 0) {
403 LWP_MutexUnlock(_mutex
);
407 LWP_MutexUnlock(_mutex
);
414 static off_t
_ISO9660_seek_r(struct _reent
*r
, int fd
, off_t pos
, int dir
) {
415 FILE_STRUCT
*file
= (FILE_STRUCT
*)fd
;
428 position
= file
->offset
+ pos
;
431 position
= file
->entry
.size
+ pos
;
438 if (pos
> 0 && position
< 0) {
439 r
->_errno
= EOVERFLOW
;
443 if (position
< 0 || position
> file
->entry
.size
) {
448 file
->offset
= position
;
453 static void stat_entry(DIR_ENTRY
*entry
, struct stat
*st
) {
455 st
->st_ino
= (ino_t
)entry
->sector
;
456 st
->st_mode
= (is_dir(entry
) ? S_IFDIR
: S_IFREG
) | (S_IRUSR
| S_IRGRP
| S_IROTH
);
460 st
->st_rdev
= st
->st_dev
;
461 st
->st_size
= entry
->size
;
468 st
->st_blksize
= SECTOR_SIZE
;
469 st
->st_blocks
= (entry
->size
+ SECTOR_SIZE
- 1) / SECTOR_SIZE
;
470 st
->st_spare4
[0] = 0;
471 st
->st_spare4
[1] = 0;
474 static int _ISO9660_fstat_r(struct _reent
*r
, int fd
, struct stat
*st
) {
475 FILE_STRUCT
*file
= (FILE_STRUCT
*)fd
;
480 stat_entry(&file
->entry
, st
);
484 static int _ISO9660_stat_r(struct _reent
*r
, const char *path
, struct stat
*st
) {
486 if (!entry_from_path(&entry
, path
)) {
490 stat_entry(&entry
, st
);
491 if (entry
.children
) free(entry
.children
);
495 static int _ISO9660_chdir_r(struct _reent
*r
, const char *path
) {
497 if (!entry_from_path(&entry
, path
)) {
500 } else if (!is_dir(&entry
)) {
504 current
= entry
.path_entry
;
505 if (entry
.children
) free(entry
.children
);
509 static DIR_ITER
*_ISO9660_diropen_r(struct _reent
*r
, DIR_ITER
*dirState
, const char *path
) {
510 DIR_STATE_STRUCT
*state
= (DIR_STATE_STRUCT
*)(dirState
->dirStruct
);
511 if (!entry_from_path(&state
->entry
, path
)) {
514 } else if (!is_dir(&state
->entry
)) {
523 static int _ISO9660_dirreset_r(struct _reent
*r
, DIR_ITER
*dirState
) {
524 DIR_STATE_STRUCT
*state
= (DIR_STATE_STRUCT
*)(dirState
->dirStruct
);
533 static int _ISO9660_dirnext_r(struct _reent
*r
, DIR_ITER
*dirState
, char *filename
, struct stat
*st
) {
534 DIR_STATE_STRUCT
*state
= (DIR_STATE_STRUCT
*)(dirState
->dirStruct
);
539 if (state
->index
>= state
->entry
.fileCount
) {
543 DIR_ENTRY
*entry
= &state
->entry
.children
[state
->index
++];
544 strncpy(filename
, entry
->name
, ISO_MAXPATHLEN
- 1);
545 stat_entry(entry
, st
);
549 static int _ISO9660_dirclose_r(struct _reent
*r
, DIR_ITER
*dirState
) {
550 DIR_STATE_STRUCT
*state
= (DIR_STATE_STRUCT
*)(dirState
->dirStruct
);
555 state
->inUse
= false;
556 if (state
->entry
.children
) free(state
->entry
.children
);
560 static const devoptab_t dotab_iso9660
= {
575 sizeof(DIR_STATE_STRUCT
),
588 unsigned long total_sector_le
, total_sect_be
;
590 unsigned long volume_set_size
, volume_seq_nr
;
591 unsigned short sector_size_le
, sector_size_be
;
592 unsigned long path_table_len_le
, path_table_len_be
;
593 unsigned long path_table_le
, path_table_2nd_le
;
594 unsigned long path_table_be
, path_table_2nd_be
;
596 char volume_set_id
[128], publisher_id
[128], data_preparer_id
[128], application_id
[128];
597 char copyright_file_id
[37], abstract_file_id
[37], bibliographical_file_id
[37];
598 } __attribute__((packed
)) VOLUME_DESCRIPTOR
;
600 static VOLUME_DESCRIPTOR
*read_volume_descriptor(u8 descriptor
) {
603 LWP_MutexLock(_mutex
);
604 for (sector
= 16; sector
< 32; sector
++) {
605 if (DI_ReadDVD(read_buffer
, 1, sector
)) {
606 LWP_MutexUnlock(_mutex
);
610 if (!memcmp(read_buffer
+ 1, "CD001\1", 6)) {
611 if (*read_buffer
== descriptor
) {
612 LWP_MutexUnlock(_mutex
);
613 return (VOLUME_DESCRIPTOR
*)read_buffer
;
615 if (*read_buffer
== 0xff) {
616 LWP_MutexUnlock(_mutex
);
623 LWP_MutexUnlock(_mutex
);
628 static PATH_ENTRY
*entry_from_index(PATH_ENTRY
*entry
, u16 index
) {
629 if (entry
->index
== index
) return entry
;
631 for (i
= 0; i
< entry
->childCount
; i
++) {
632 PATH_ENTRY
*match
= entry_from_index(&entry
->children
[i
], index
);
633 if (match
) return match
;
638 static PATH_ENTRY
*add_child_entry(PATH_ENTRY
*dir
) {
639 PATH_ENTRY
*newChildren
= realloc(dir
->children
, (dir
->childCount
+ 1) * sizeof(PATH_ENTRY
));
640 if (!newChildren
) return NULL
;
641 bzero(newChildren
+ dir
->childCount
, sizeof(PATH_ENTRY
));
642 dir
->children
= newChildren
;
643 PATH_ENTRY
*child
= &dir
->children
[dir
->childCount
++];
647 static bool read_directories() {
648 VOLUME_DESCRIPTOR
*volume
= read_volume_descriptor(2);
649 if (volume
) unicode
= true;
650 else if (!(volume
= read_volume_descriptor(1))) return false;
652 if (!(root
= malloc(sizeof(PATH_ENTRY
)))) return false;
653 bzero(root
, sizeof(PATH_ENTRY
));
654 root
->table_entry
.name_length
= 1;
655 root
->table_entry
.extended_sectors
= volume
->root
[OFFSET_EXTENDED
];
656 root
->table_entry
.sector
= *(u32
*)(volume
->root
+ OFFSET_SECTOR
);
657 root
->table_entry
.parent
= 0;
658 root
->table_entry
.name
[0] = '\x00';
662 u32 path_table
= volume
->path_table_be
;
663 u32 path_table_len
= volume
->path_table_len_be
;
665 u64 offset
= sizeof(PATHTABLE_ENTRY
) - ISO_MAXPATHLEN
+ 2;
666 PATH_ENTRY
*parent
= root
;
667 while (i
< 0xffff && offset
< path_table_len
) {
668 PATHTABLE_ENTRY entry
;
670 LWP_MutexLock(_mutex
);
671 if (_read(&entry
, (u64
)path_table
* SECTOR_SIZE
+ offset
, sizeof(PATHTABLE_ENTRY
)) != sizeof(PATHTABLE_ENTRY
)) {
672 LWP_MutexUnlock(_mutex
);
673 return false; // kinda dodgy - could be reading too far
675 LWP_MutexUnlock(_mutex
);
677 if (parent
->index
!= entry
.parent
)
678 parent
= entry_from_index(root
, entry
.parent
);
682 PATH_ENTRY
*child
= add_child_entry(parent
);
686 memcpy(&child
->table_entry
, &entry
, sizeof(PATHTABLE_ENTRY
));
687 offset
+= sizeof(PATHTABLE_ENTRY
) - ISO_MAXPATHLEN
+ child
->table_entry
.name_length
;
688 if (child
->table_entry
.name_length
% 2) offset
++;
693 for (i
= 0; i
< (child
->table_entry
.name_length
/ 2); i
++)
694 child
->table_entry
.name
[i
] = entry
.name
[i
* 2 + 1];
695 child
->table_entry
.name
[i
] = '\x00';
696 child
->table_entry
.name_length
= i
;
698 child
->table_entry
.name
[child
->table_entry
.name_length
] = '\x00';
705 static void cleanup_recursive(PATH_ENTRY
*entry
) {
707 for (i
= 0; i
< entry
->childCount
; i
++)
708 cleanup_recursive(&entry
->children
[i
]);
709 if (entry
->children
) free(entry
->children
);
712 bool ISO9660_Mount() {
714 bool success
= read_directories() && (dotab_device
= AddDevice(&dotab_iso9660
)) >= 0;
715 if (success
) last_access
= gettime();
716 else ISO9660_Unmount();
718 LWP_MutexInit(&_mutex
, false);
723 bool ISO9660_Unmount() {
725 cleanup_recursive(root
);
735 if (dotab_device
>= 0) {
737 return !RemoveDevice(DEVICE_NAME
":");
741 LWP_MutexDestroy(_mutex
);
748 u64
ISO9660_LastAccess() {