diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77ec36d..9f3d986 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: build: runs-on: ubuntu-latest # Something something old releases being hostile and damaging, boo hoo dkp. Maybe don't make extremely breaking changes. - container: devkitpro/devkitarm + container: devkitpro/devkitarm:20241104 name: Build with Docker using devkitARM steps: - name: Checkout repo @@ -114,4 +114,4 @@ jobs: CONTENT_LENGTH="Content-Length: $(stat -c%s $file)" UPLOAD_URL="https://uploads.github.com/repos/${{ github.repository }}/releases/$ID/assets?name=$(basename $file)" curl -XPOST -H "$AUTH_HEADER" -H "$CONTENT_LENGTH" -H "$CONTENT_TYPE" --upload-file "$file" "$UPLOAD_URL" - done + done \ No newline at end of file diff --git a/README.md b/README.md index 97d8f43..3d509f3 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,6 @@ This is the best DSi NAND repair tool out there, offering features such as: - Lots of useful NAND diagnostic info Why do these matter? You can create a new working NAND from scratch! No backups needed (but still recommended)! + +## Notes +- I am including my own hostile and outdated fork of libfat. This is to block `nand_Startup()` during `fatMount()`. Without this having a NAND re-mount would run `nand_Startup()` more than once and break every NAND R/W function until reboot... \ No newline at end of file diff --git a/arm7/src/main.c b/arm7/src/main.c index 2ff99d1..b3b148b 100644 --- a/arm7/src/main.c +++ b/arm7/src/main.c @@ -188,6 +188,7 @@ int main() fifoSendValue32(FIFO_USER_03, batteryStatus); fifoSendValue32(FIFO_USER_01, *(vu8*)0x04004024); + swiWaitForVBlank(); } diff --git a/arm9/Makefile b/arm9/Makefile index 1637712..034ef47 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -39,8 +39,8 @@ endif # all directories are relative to this makefile #--------------------------------------------------------------------------------- BUILD := build -SOURCES := src src/nand src/nand/polarssl src/nand/twltool -INCLUDES := include src/nand +SOURCES := src src/nand src/nand/polarssl src/nand/twltool src/lib/libfat/source +INCLUDES := include src/nand src/lib/libfat/include DATA := ../data GRAPHICS := fonts #--------------------------------------------------------------------------------- @@ -63,7 +63,7 @@ LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) #--------------------------------------------------------------------------------- # any extra libraries we wish to link with the project #--------------------------------------------------------------------------------- -LIBS := -lfilesystem -lfat -lnds9 +LIBS := -lfilesystem -lnds9 #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing # include and lib diff --git a/arm9/src/lib/libfat/include/fat.h b/arm9/src/lib/libfat/include/fat.h new file mode 100644 index 0000000..f123743 --- /dev/null +++ b/arm9/src/lib/libfat/include/fat.h @@ -0,0 +1,122 @@ +/* + fat.h + Simple functionality for startup, mounting and unmounting of FAT-based devices. + + Copyright (c) 2006 - 2012 + Michael "Chishm" Chisholm + Dave "WinterMute" Murphy + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef _LIBFAT_H +#define _LIBFAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libfatversion.h" + +// When compiling for NDS, make sure NDS is defined +#ifndef NDS + #if defined ARM9 || defined ARM7 + #define NDS + #endif +#endif + +#include + +#if defined(__gamecube__) || defined (__wii__) +# include +#else +# ifdef NDS +# include +# else +# include +# endif +#endif + +/* +Initialise any inserted block-devices. +Add the fat device driver to the devoptab, making it available for standard file functions. +cacheSize: The number of pages to allocate for each inserted block-device +setAsDefaultDevice: if true, make this the default device driver for file operations +*/ +extern bool fatInit (uint32_t cacheSize, bool setAsDefaultDevice); + +/* +Calls fatInit with setAsDefaultDevice = true and cacheSize optimised for the host system. +*/ +extern bool fatInitDefault (void); + +/* +Mount the device pointed to by interface, and set up a devoptab entry for it as "name:". +You can then access the filesystem using "name:/". +This will mount the active partition or the first valid partition on the disc, +and will use a cache size optimized for the host system. +*/ +extern bool fatMountSimple (const char* name, const DISC_INTERFACE* interface, bool isNand); + +/* +Mount the device pointed to by interface, and set up a devoptab entry for it as "name:". +You can then access the filesystem using "name:/". +If startSector = 0, it will mount the active partition of the first valid partition on +the disc. Otherwise it will try to mount the partition starting at startSector. +cacheSize specifies the number of pages to allocate for the cache. +This will not startup the disc, so you need to call interface->startup(); first. +*/ +extern bool fatMount (const char* name, const DISC_INTERFACE* interface, sec_t startSector, uint32_t cacheSize, uint32_t SectorsPerPage, bool isNand); + +/* +Unmount the partition specified by name. +If there are open files, it will attempt to synchronise them to disc. +*/ +extern void fatUnmount (const char* name); + +/* +Get Volume Label +*/ +extern void fatGetVolumeLabel (const char* name, char *label); + +// File attributes +#define ATTR_ARCHIVE 0x20 // Archive +#define ATTR_DIRECTORY 0x10 // Directory +#define ATTR_VOLUME 0x08 // Volume +#define ATTR_SYSTEM 0x04 // System +#define ATTR_HIDDEN 0x02 // Hidden +#define ATTR_READONLY 0x01 // Read only + +/* +Methods to modify DOS File Attributes +*/ +int FAT_getAttr(const char *file); +int FAT_setAttr(const char *file, uint8_t attr ); + +#define LIBFAT_FEOS_MULTICWD + +#ifdef __cplusplus +} +#endif + +#endif // _LIBFAT_H diff --git a/arm9/src/lib/libfat/source/bit_ops.h b/arm9/src/lib/libfat/source/bit_ops.h new file mode 100644 index 0000000..762be0b --- /dev/null +++ b/arm9/src/lib/libfat/source/bit_ops.h @@ -0,0 +1,57 @@ +/* + bit_ops.h + Functions for dealing with conversion of data between types + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _BIT_OPS_H +#define _BIT_OPS_H + +#include + +/*----------------------------------------------------------------- +Functions to deal with little endian values stored in uint8_t arrays +-----------------------------------------------------------------*/ +static inline uint16_t u8array_to_u16 (const uint8_t* item, int offset) { + return ( item[offset] | (item[offset + 1] << 8)); +} + +static inline uint32_t u8array_to_u32 (const uint8_t* item, int offset) { + return ( item[offset] | (item[offset + 1] << 8) | (item[offset + 2] << 16) | (item[offset + 3] << 24)); +} + +static inline void u16_to_u8array (uint8_t* item, int offset, uint16_t value) { + item[offset] = (uint8_t) value; + item[offset + 1] = (uint8_t)(value >> 8); +} + +static inline void u32_to_u8array (uint8_t* item, int offset, uint32_t value) { + item[offset] = (uint8_t) value; + item[offset + 1] = (uint8_t)(value >> 8); + item[offset + 2] = (uint8_t)(value >> 16); + item[offset + 3] = (uint8_t)(value >> 24); +} + +#endif // _BIT_OPS_H diff --git a/arm9/src/lib/libfat/source/cache.c b/arm9/src/lib/libfat/source/cache.c new file mode 100644 index 0000000..07576b9 --- /dev/null +++ b/arm9/src/lib/libfat/source/cache.c @@ -0,0 +1,323 @@ +/* + cache.c + The cache is not visible to the user. It should be flushed + when any file is closed or changes are made to the filesystem. + + This cache implements a least-used-page replacement policy. This will + distribute sectors evenly over the pages, so if less than the maximum + pages are used at once, they should all eventually remain in the cache. + This also has the benefit of throwing out old sectors, so as not to keep + too many stale pages around. + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include + +#include "common.h" +#include "cache.h" +#include "disc.h" + +#include "mem_allocate.h" +#include "bit_ops.h" +#include "file_allocation_table.h" + +#define CACHE_FREE UINT_MAX + +CACHE* _FAT_cache_constructor (unsigned int numberOfPages, unsigned int sectorsPerPage, const DISC_INTERFACE* discInterface, sec_t endOfPartition, unsigned int bytesPerSector) { + CACHE* cache; + unsigned int i; + CACHE_ENTRY* cacheEntries; + + if (numberOfPages < 2) { + numberOfPages = 2; + } + + if (sectorsPerPage < 8) { + sectorsPerPage = 8; + } + + cache = (CACHE*) _FAT_mem_allocate (sizeof(CACHE)); + if (cache == NULL) { + return NULL; + } + + cache->disc = discInterface; + cache->endOfPartition = endOfPartition; + cache->numberOfPages = numberOfPages; + cache->sectorsPerPage = sectorsPerPage; + cache->bytesPerSector = bytesPerSector; + + + cacheEntries = (CACHE_ENTRY*) _FAT_mem_allocate ( sizeof(CACHE_ENTRY) * numberOfPages); + if (cacheEntries == NULL) { + _FAT_mem_free (cache); + return NULL; + } + + for (i = 0; i < numberOfPages; i++) { + cacheEntries[i].sector = CACHE_FREE; + cacheEntries[i].count = 0; + cacheEntries[i].last_access = 0; + cacheEntries[i].dirty = false; + cacheEntries[i].cache = (uint8_t*) _FAT_mem_align ( sectorsPerPage * bytesPerSector ); + } + + cache->cacheEntries = cacheEntries; + + return cache; +} + +void _FAT_cache_destructor (CACHE* cache) { + unsigned int i; + // Clear out cache before destroying it + _FAT_cache_flush(cache); + + // Free memory in reverse allocation order + for (i = 0; i < cache->numberOfPages; i++) { + _FAT_mem_free (cache->cacheEntries[i].cache); + } + _FAT_mem_free (cache->cacheEntries); + _FAT_mem_free (cache); +} + + +static u32 accessCounter = 0; + +static u32 accessTime(){ + accessCounter++; + return accessCounter; +} + + +static CACHE_ENTRY* _FAT_cache_getPage(CACHE *cache,sec_t sector) +{ + unsigned int i; + CACHE_ENTRY* cacheEntries = cache->cacheEntries; + unsigned int numberOfPages = cache->numberOfPages; + unsigned int sectorsPerPage = cache->sectorsPerPage; + + bool foundFree = false; + unsigned int oldUsed = 0; + unsigned int oldAccess = UINT_MAX; + + for(i=0;i=cacheEntries[i].sector && sector<(cacheEntries[i].sector + cacheEntries[i].count)) { + cacheEntries[i].last_access = accessTime(); + return &(cacheEntries[i]); + } + + if(foundFree==false && (cacheEntries[i].sector==CACHE_FREE || cacheEntries[i].last_accessdisc,cacheEntries[oldUsed].sector,cacheEntries[oldUsed].count,cacheEntries[oldUsed].cache)) return NULL; + cacheEntries[oldUsed].dirty = false; + } + + sector = (sector/sectorsPerPage)*sectorsPerPage; // align base sector to page size + sec_t next_page = sector + sectorsPerPage; + if(next_page > cache->endOfPartition) next_page = cache->endOfPartition; + + if(!_FAT_disc_readSectors(cache->disc,sector,next_page-sector,cacheEntries[oldUsed].cache)) return NULL; + + cacheEntries[oldUsed].sector = sector; + cacheEntries[oldUsed].count = next_page-sector; + cacheEntries[oldUsed].last_access = accessTime(); + + return &(cacheEntries[oldUsed]); +} + +bool _FAT_cache_readSectors(CACHE *cache,sec_t sector,sec_t numSectors,void *buffer) +{ + sec_t sec; + sec_t secs_to_read; + CACHE_ENTRY *entry; + uint8_t *dest = (uint8_t *)buffer; + + while(numSectors>0) { + entry = _FAT_cache_getPage(cache,sector); + if(entry==NULL) return false; + + sec = sector - entry->sector; + secs_to_read = entry->count - sec; + if(secs_to_read>numSectors) secs_to_read = numSectors; + + memcpy(dest,entry->cache + (sec*cache->bytesPerSector),(secs_to_read*cache->bytesPerSector)); + + dest += (secs_to_read*cache->bytesPerSector); + sector += secs_to_read; + numSectors -= secs_to_read; + } + + return true; +} + +/* +Reads some data from a cache page, determined by the sector number +*/ +bool _FAT_cache_readPartialSector (CACHE* cache, void* buffer, sec_t sector, unsigned int offset, size_t size) +{ + sec_t sec; + CACHE_ENTRY *entry; + + if (offset + size > cache->bytesPerSector) return false; + + entry = _FAT_cache_getPage(cache,sector); + if(entry==NULL) return false; + + sec = sector - entry->sector; + memcpy(buffer,entry->cache + ((sec*cache->bytesPerSector) + offset),size); + + return true; +} + +bool _FAT_cache_readLittleEndianValue (CACHE* cache, uint32_t *value, sec_t sector, unsigned int offset, int num_bytes) { + uint8_t buf[4]; + if (!_FAT_cache_readPartialSector(cache, buf, sector, offset, num_bytes)) return false; + + switch(num_bytes) { + case 1: *value = buf[0]; break; + case 2: *value = u8array_to_u16(buf,0); break; + case 4: *value = u8array_to_u32(buf,0); break; + default: return false; + } + return true; +} + +/* +Writes some data to a cache page, making sure it is loaded into memory first. +*/ +bool _FAT_cache_writePartialSector (CACHE* cache, const void* buffer, sec_t sector, unsigned int offset, size_t size) +{ + sec_t sec; + CACHE_ENTRY *entry; + + if (offset + size > cache->bytesPerSector) return false; + + entry = _FAT_cache_getPage(cache,sector); + if(entry==NULL) return false; + + sec = sector - entry->sector; + memcpy(entry->cache + ((sec*cache->bytesPerSector) + offset),buffer,size); + + entry->dirty = true; + return true; +} + +bool _FAT_cache_writeLittleEndianValue (CACHE* cache, const uint32_t value, sec_t sector, unsigned int offset, int size) { + uint8_t buf[4] = {0, 0, 0, 0}; + + switch(size) { + case 1: buf[0] = value; break; + case 2: u16_to_u8array(buf, 0, value); break; + case 4: u32_to_u8array(buf, 0, value); break; + default: return false; + } + + return _FAT_cache_writePartialSector(cache, buf, sector, offset, size); +} + +/* +Writes some data to a cache page, zeroing out the page first +*/ +bool _FAT_cache_eraseWritePartialSector (CACHE* cache, const void* buffer, sec_t sector, unsigned int offset, size_t size) +{ + sec_t sec; + CACHE_ENTRY *entry; + + if (offset + size > cache->bytesPerSector) return false; + + entry = _FAT_cache_getPage(cache,sector); + if(entry==NULL) return false; + + sec = sector - entry->sector; + memset(entry->cache + (sec*cache->bytesPerSector),0,cache->bytesPerSector); + memcpy(entry->cache + ((sec*cache->bytesPerSector) + offset),buffer,size); + + entry->dirty = true; + return true; +} + + +bool _FAT_cache_writeSectors (CACHE* cache, sec_t sector, sec_t numSectors, const void* buffer) +{ + sec_t sec; + sec_t secs_to_write; + CACHE_ENTRY* entry; + const uint8_t *src = (const uint8_t *)buffer; + + while(numSectors>0) + { + entry = _FAT_cache_getPage(cache,sector); + if(entry==NULL) return false; + + sec = sector - entry->sector; + secs_to_write = entry->count - sec; + if(secs_to_write>numSectors) secs_to_write = numSectors; + + memcpy(entry->cache + (sec*cache->bytesPerSector),src,(secs_to_write*cache->bytesPerSector)); + + src += (secs_to_write*cache->bytesPerSector); + sector += secs_to_write; + numSectors -= secs_to_write; + + entry->dirty = true; + } + return true; +} + +/* +Flushes all dirty pages to disc, clearing the dirty flag. +*/ +bool _FAT_cache_flush (CACHE* cache) { + unsigned int i; + + for (i = 0; i < cache->numberOfPages; i++) { + if (cache->cacheEntries[i].dirty) { + if (!_FAT_disc_writeSectors (cache->disc, cache->cacheEntries[i].sector, cache->cacheEntries[i].count, cache->cacheEntries[i].cache)) { + return false; + } + } + cache->cacheEntries[i].dirty = false; + } + + return true; +} + +void _FAT_cache_invalidate (CACHE* cache) { + unsigned int i; + _FAT_cache_flush(cache); + for (i = 0; i < cache->numberOfPages; i++) { + cache->cacheEntries[i].sector = CACHE_FREE; + cache->cacheEntries[i].last_access = 0; + cache->cacheEntries[i].count = 0; + cache->cacheEntries[i].dirty = false; + } +} diff --git a/arm9/src/lib/libfat/source/cache.h b/arm9/src/lib/libfat/source/cache.h new file mode 100644 index 0000000..07bb14c --- /dev/null +++ b/arm9/src/lib/libfat/source/cache.h @@ -0,0 +1,128 @@ +/* + cache.h + The cache is not visible to the user. It should be flushed + when any file is closed or changes are made to the filesystem. + + This cache implements a least-used-page replacement policy. This will + distribute sectors evenly over the pages, so if less than the maximum + pages are used at once, they should all eventually remain in the cache. + This also has the benefit of throwing out old sectors, so as not to keep + too many stale pages around. + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _CACHE_H +#define _CACHE_H + +#include "common.h" +#include "disc.h" + +typedef struct { + sec_t sector; + unsigned int count; + unsigned int last_access; + bool dirty; + uint8_t* cache; +} CACHE_ENTRY; + +typedef struct { + const DISC_INTERFACE* disc; + sec_t endOfPartition; + unsigned int numberOfPages; + unsigned int sectorsPerPage; + unsigned int bytesPerSector; + CACHE_ENTRY* cacheEntries; +} CACHE; + +/* +Read data from a sector in the cache +If the sector is not in the cache, it will be swapped in +offset is the position to start reading from +size is the amount of data to read +Precondition: offset + size <= BYTES_PER_READ +*/ +bool _FAT_cache_readPartialSector (CACHE* cache, void* buffer, sec_t sector, unsigned int offset, size_t size); + +bool _FAT_cache_readLittleEndianValue (CACHE* cache, uint32_t *value, sec_t sector, unsigned int offset, int num_bytes); + +/* +Write data to a sector in the cache +If the sector is not in the cache, it will be swapped in. +When the sector is swapped out, the data will be written to the disc +offset is the position to start writing to +size is the amount of data to write +Precondition: offset + size <= BYTES_PER_READ +*/ +bool _FAT_cache_writePartialSector (CACHE* cache, const void* buffer, sec_t sector, unsigned int offset, size_t size); + +bool _FAT_cache_writeLittleEndianValue (CACHE* cache, const uint32_t value, sec_t sector, unsigned int offset, int num_bytes); + +/* +Write data to a sector in the cache, zeroing the sector first +If the sector is not in the cache, it will be swapped in. +When the sector is swapped out, the data will be written to the disc +offset is the position to start writing to +size is the amount of data to write +Precondition: offset + size <= BYTES_PER_READ +*/ +bool _FAT_cache_eraseWritePartialSector (CACHE* cache, const void* buffer, sec_t sector, unsigned int offset, size_t size); + +/* +Read several sectors from the cache +*/ +bool _FAT_cache_readSectors (CACHE* cache, sec_t sector, sec_t numSectors, void* buffer); + +/* +Read a full sector from the cache +*/ +static inline bool _FAT_cache_readSector (CACHE* cache, void* buffer, sec_t sector) { + return _FAT_cache_readPartialSector (cache, buffer, sector, 0, cache->bytesPerSector); +} + +/* +Write a full sector to the cache +*/ +static inline bool _FAT_cache_writeSector (CACHE* cache, const void* buffer, sec_t sector) { + return _FAT_cache_writePartialSector (cache, buffer, sector, 0, cache->bytesPerSector); +} + +bool _FAT_cache_writeSectors (CACHE* cache, sec_t sector, sec_t numSectors, const void* buffer); + +/* +Write any dirty sectors back to disc and clear out the contents of the cache +*/ +bool _FAT_cache_flush (CACHE* cache); + +/* +Clear out the contents of the cache without writing any dirty sectors first +*/ +void _FAT_cache_invalidate (CACHE* cache); + +CACHE* _FAT_cache_constructor (unsigned int numberOfPages, unsigned int sectorsPerPage, const DISC_INTERFACE* discInterface, sec_t endOfPartition, unsigned int bytesPerSector); + +void _FAT_cache_destructor (CACHE* cache); + +#endif // _CACHE_H + diff --git a/arm9/src/lib/libfat/source/common.h b/arm9/src/lib/libfat/source/common.h new file mode 100644 index 0000000..d5a58f6 --- /dev/null +++ b/arm9/src/lib/libfat/source/common.h @@ -0,0 +1,84 @@ +/* + common.h + Common definitions and included files for the FATlib + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _COMMON_H +#define _COMMON_H + +#include +#include +#include + +// When compiling for NDS, make sure NDS is defined +#ifndef NDS + #if defined ARM9 || defined ARM7 + #define NDS + #endif +#endif + +// Platform specific includes +#if defined(__gamecube__) || defined (__wii__) + #include + #include + #include +#elif defined(NDS) + #include + #include + #include +#elif defined(GBA) + #include + #include +#elif defined(GP2X) + #include + #include +#endif + +// Platform specific options +#if defined (__wii__) + #define DEFAULT_CACHE_PAGES 4 + #define DEFAULT_SECTORS_PAGE 64 + #define USE_LWP_LOCK + #define USE_RTC_TIME +#elif defined (__gamecube__) + #define DEFAULT_CACHE_PAGES 4 + #define DEFAULT_SECTORS_PAGE 64 + #define USE_LWP_LOCK + #define USE_RTC_TIME +#elif defined (NDS) + #define DEFAULT_CACHE_PAGES 16 + #define DEFAULT_SECTORS_PAGE 8 + #define USE_RTC_TIME +#elif defined (GBA) + #define DEFAULT_CACHE_PAGES 2 + #define DEFAULT_SECTORS_PAGE 8 + #define LIMIT_SECTORS 128 +#elif defined (GP2X) + #define DEFAULT_CACHE_PAGES 16 + #define DEFAULT_SECTORS_PAGE 8 +#endif + +#endif // _COMMON_H diff --git a/arm9/src/lib/libfat/source/directory.c b/arm9/src/lib/libfat/source/directory.c new file mode 100644 index 0000000..e3aa930 --- /dev/null +++ b/arm9/src/lib/libfat/source/directory.c @@ -0,0 +1,1124 @@ +/* + directory.c + Reading, writing and manipulation of the directory structure on + a FAT partition + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include + +#include "directory.h" +#include "common.h" +#include "partition.h" +#include "file_allocation_table.h" +#include "bit_ops.h" +#include "filetime.h" + +// Directory entry codes +#define DIR_ENTRY_LAST 0x00 +#define DIR_ENTRY_FREE 0xE5 + +typedef unsigned short ucs2_t; + +// Long file name directory entry +enum LFN_offset { + LFN_offset_ordinal = 0x00, // Position within LFN + LFN_offset_char0 = 0x01, + LFN_offset_char1 = 0x03, + LFN_offset_char2 = 0x05, + LFN_offset_char3 = 0x07, + LFN_offset_char4 = 0x09, + LFN_offset_flag = 0x0B, // Should be equal to ATTRIB_LFN + LFN_offset_reserved1 = 0x0C, // Always 0x00 + LFN_offset_checkSum = 0x0D, // Checksum of short file name (alias) + LFN_offset_char5 = 0x0E, + LFN_offset_char6 = 0x10, + LFN_offset_char7 = 0x12, + LFN_offset_char8 = 0x14, + LFN_offset_char9 = 0x16, + LFN_offset_char10 = 0x18, + LFN_offset_reserved2 = 0x1A, // Always 0x0000 + LFN_offset_char11 = 0x1C, + LFN_offset_char12 = 0x1E +}; +static const int LFN_offset_table[13]={0x01,0x03,0x05,0x07,0x09,0x0E,0x10,0x12,0x14,0x16,0x18,0x1C,0x1E}; + +#define LFN_END 0x40 +#define LFN_DEL 0x80 + +static const char ILLEGAL_ALIAS_CHARACTERS[] = "\\/:;*?\"<>|&+,=[] "; +static const char ILLEGAL_LFN_CHARACTERS[] = "\\/:*?\"<>|"; + +/* +Returns number of UCS-2 characters needed to encode an LFN +Returns -1 if it is an invalid LFN +*/ +#define ABOVE_UCS_RANGE 0xF0 +static int _FAT_directory_lfnLength (const char* name) { + unsigned int i; + size_t nameLength; + int ucsLength; + const char* tempName = name; + + nameLength = strnlen(name, NAME_MAX); + // Make sure the name is short enough to be valid + if ( nameLength >= NAME_MAX) { + return -1; + } + // Make sure it doesn't contain any invalid characters + if (strpbrk (name, ILLEGAL_LFN_CHARACTERS) != NULL) { + return -1; + } + // Make sure the name doesn't contain any control codes or codes not representable in UCS-2 + for (i = 0; i < nameLength; i++) { + unsigned char ch = (unsigned char) name[i]; + if (ch < 0x20 || ch >= ABOVE_UCS_RANGE) { + return -1; + } + } + // Convert to UCS-2 and get the resulting length + ucsLength = mbsrtowcs(NULL, &tempName, MAX_LFN_LENGTH, NULL); + if (ucsLength < 0 || ucsLength >= MAX_LFN_LENGTH) { + return -1; + } + + // Otherwise it is valid + return ucsLength; +} + +/* +Convert a multibyte encoded string into a NUL-terminated UCS-2 string, storing at most len characters +return number of characters stored +*/ +static size_t _FAT_directory_mbstoucs2 (ucs2_t* dst, const char* src, size_t len) { + mbstate_t ps = {0}; + wchar_t tempChar; + int bytes; + size_t count = 0; + + while (count < len-1 && *src != '\0') { + bytes = mbrtowc (&tempChar, src, MB_CUR_MAX, &ps); + if (bytes > 0) { + *dst = (ucs2_t)tempChar; + src += bytes; + dst++; + count++; + } else if (bytes == 0) { + break; + } else { + return -1; + } + } + *dst = '\0'; + + return count; +} + +/* +Convert a UCS-2 string into a NUL-terminated multibyte string, storing at most len chars +return number of chars stored, or (size_t)-1 on error +*/ +static size_t _FAT_directory_ucs2tombs (char* dst, const ucs2_t* src, size_t len) { + mbstate_t ps = {0}; + size_t count = 0; + int bytes; + char buff[MB_CUR_MAX]; + int i; + + while (count < len - 1 && *src != '\0') { + bytes = wcrtomb (buff, *src, &ps); + if (bytes < 0) { + return -1; + } + if (count + bytes < len && bytes > 0) { + for (i = 0; i < bytes; i++) { + *dst++ = buff[i]; + } + src++; + count += bytes; + } else { + break; + } + } + *dst = L'\0'; + + return count; +} + +/* +Case-independent comparison of two multibyte encoded strings +*/ +static int _FAT_directory_mbsncasecmp (const char* s1, const char* s2, size_t len1) { + wchar_t wc1, wc2; + mbstate_t ps1 = {0}; + mbstate_t ps2 = {0}; + size_t b1 = 0; + size_t b2 = 0; + + if (len1 == 0) { + return 0; + } + + do { + s1 += b1; + s2 += b2; + b1 = mbrtowc(&wc1, s1, MB_CUR_MAX, &ps1); + b2 = mbrtowc(&wc2, s2, MB_CUR_MAX, &ps2); + if ((int)b1 < 0 || (int)b2 < 0) { + break; + } + len1 -= b1; + } while (len1 > 0 && towlower(wc1) == towlower(wc2) && wc1 != 0); + + return towlower(wc1) - towlower(wc2); +} + + +static bool _FAT_directory_entryGetAlias (const u8* entryData, char* destName) { + char c; + bool caseInfo; + int i = 0; + int j = 0; + + destName[0] = '\0'; + if (entryData[0] != DIR_ENTRY_FREE) { + if (entryData[0] == '.') { + destName[0] = '.'; + if (entryData[1] == '.') { + destName[1] = '.'; + destName[2] = '\0'; + } else { + destName[1] = '\0'; + } + } else { + // Copy the filename from the dirEntry to the string + caseInfo = entryData[DIR_ENTRY_caseInfo] & CASE_LOWER_BASE; + for (i = 0; (i < 8) && (entryData[DIR_ENTRY_name + i] != ' '); i++) { + c = entryData[DIR_ENTRY_name + i]; + destName[i] = (caseInfo ? tolower((unsigned char)c) : c); + } + // Copy the extension from the dirEntry to the string + if (entryData[DIR_ENTRY_extension] != ' ') { + destName[i++] = '.'; + caseInfo = entryData[DIR_ENTRY_caseInfo] & CASE_LOWER_EXT; + for ( j = 0; (j < 3) && (entryData[DIR_ENTRY_extension + j] != ' '); j++) { + c = entryData[DIR_ENTRY_extension + j]; + destName[i++] = (caseInfo ? tolower((unsigned char)c) : c); + } + } + destName[i] = '\0'; + } + } + + return (destName[0] != '\0'); +} + +uint32_t _FAT_directory_entryGetCluster (PARTITION* partition, const uint8_t* entryData) { + if (partition->filesysType == FS_FAT32) { + // Only use high 16 bits of start cluster when we are certain they are correctly defined + return u8array_to_u16(entryData,DIR_ENTRY_cluster) | (u8array_to_u16(entryData, DIR_ENTRY_clusterHigh) << 16); + } else { + return u8array_to_u16(entryData,DIR_ENTRY_cluster); + } +} + +static bool _FAT_directory_incrementDirEntryPosition (PARTITION* partition, DIR_ENTRY_POSITION* entryPosition, bool extendDirectory) { + DIR_ENTRY_POSITION position = *entryPosition; + uint32_t tempCluster; + + // Increment offset, wrapping at the end of a sector + ++ position.offset; + if (position.offset == partition->bytesPerSector / DIR_ENTRY_DATA_SIZE) { + position.offset = 0; + // Increment sector when wrapping + ++ position.sector; + // But wrap at the end of a cluster + if ((position.sector == partition->sectorsPerCluster) && (position.cluster != FAT16_ROOT_DIR_CLUSTER)) { + position.sector = 0; + // Move onto the next cluster, making sure there is another cluster to go to + tempCluster = _FAT_fat_nextCluster(partition, position.cluster); + if (tempCluster == CLUSTER_EOF) { + if (extendDirectory) { + tempCluster = _FAT_fat_linkFreeClusterCleared (partition, position.cluster); + if (!_FAT_fat_isValidCluster(partition, tempCluster)) { + return false; // This will only happen if the disc is full + } + } else { + return false; // Got to the end of the directory, not extending it + } + } + position.cluster = tempCluster; + } else if ((position.cluster == FAT16_ROOT_DIR_CLUSTER) && (position.sector == (partition->dataStart - partition->rootDirStart))) { + return false; // Got to end of root directory, can't extend it + } + } + *entryPosition = position; + return true; +} + +bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) { + DIR_ENTRY_POSITION entryStart; + DIR_ENTRY_POSITION entryEnd; + uint8_t entryData[0x20]; + ucs2_t lfn[MAX_LFN_LENGTH]; + bool notFound, found; + int lfnPos; + uint8_t lfnChkSum, chkSum; + bool lfnExists; + int i; + + lfnChkSum = 0; + + entryStart = entry->dataEnd; + + // Make sure we are using the correct root directory, in case of FAT32 + if (entryStart.cluster == FAT16_ROOT_DIR_CLUSTER) { + entryStart.cluster = partition->rootDirCluster; + } + + entryEnd = entryStart; + + lfnExists = false; + + found = false; + notFound = false; + + while (!found && !notFound) { + if (_FAT_directory_incrementDirEntryPosition (partition, &entryEnd, false) == false) { + notFound = true; + break; + } + + _FAT_cache_readPartialSector (partition->cache, entryData, + _FAT_fat_clusterToSector(partition, entryEnd.cluster) + entryEnd.sector, + entryEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + + if (entryData[DIR_ENTRY_attributes] == ATTRIB_LFN) { + // It's an LFN + if (entryData[LFN_offset_ordinal] & LFN_DEL) { + lfnExists = false; + } else if (entryData[LFN_offset_ordinal] & LFN_END) { + // Last part of LFN, make sure it isn't deleted using previous if(Thanks MoonLight) + entryStart = entryEnd; // This is the start of a directory entry + lfnExists = true; + lfnPos = (entryData[LFN_offset_ordinal] & ~LFN_END) * 13; + if (lfnPos > MAX_LFN_LENGTH - 1) { + lfnPos = MAX_LFN_LENGTH - 1; + } + lfn[lfnPos] = '\0'; // Set end of lfn to null character + lfnChkSum = entryData[LFN_offset_checkSum]; + } + if (lfnChkSum != entryData[LFN_offset_checkSum]) { + lfnExists = false; + } + if (lfnExists) { + lfnPos = ((entryData[LFN_offset_ordinal] & ~LFN_END) - 1) * 13; + for (i = 0; i < 13; i++) { + if (lfnPos + i < MAX_LFN_LENGTH - 1) { + lfn[lfnPos + i] = entryData[LFN_offset_table[i]] | (entryData[LFN_offset_table[i]+1] << 8); + } + } + } + } else if (entryData[DIR_ENTRY_attributes] & ATTRIB_VOL) { + // This is a volume name, don't bother with it + } else if (entryData[0] == DIR_ENTRY_LAST) { + notFound = true; + } else if ((entryData[0] != DIR_ENTRY_FREE) && (entryData[0] > 0x20) && !(entryData[DIR_ENTRY_attributes] & ATTRIB_VOL)) { + if (lfnExists) { + // Calculate file checksum + chkSum = 0; + for (i=0; i < 11; i++) { + // NOTE: The operation is an unsigned char rotate right + chkSum = ((chkSum & 1) ? 0x80 : 0) + (chkSum >> 1) + entryData[i]; + } + if (chkSum != lfnChkSum) { + lfnExists = false; + entry->filename[0] = '\0'; + } + } + + if (lfnExists) { + if (_FAT_directory_ucs2tombs (entry->filename, lfn, NAME_MAX) == (size_t)-1) { + // Failed to convert the file name to UTF-8. Maybe the wrong locale is set? + return false; + } + } else { + entryStart = entryEnd; + _FAT_directory_entryGetAlias (entryData, entry->filename); + } + found = true; + } + } + + // If no file is found, return false + if (notFound) { + return false; + } else { + // Fill in the directory entry struct + entry->dataStart = entryStart; + entry->dataEnd = entryEnd; + memcpy (entry->entryData, entryData, DIR_ENTRY_DATA_SIZE); + return true; + } +} + +bool _FAT_directory_getFirstEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster) { + entry->dataStart.cluster = dirCluster; + entry->dataStart.sector = 0; + entry->dataStart.offset = -1; // Start before the beginning of the directory + + entry->dataEnd = entry->dataStart; + + return _FAT_directory_getNextEntry (partition, entry); +} + +bool _FAT_directory_getRootEntry (PARTITION* partition, DIR_ENTRY* entry) { + entry->dataStart.cluster = 0; + entry->dataStart.sector = 0; + entry->dataStart.offset = 0; + + entry->dataEnd = entry->dataStart; + + memset (entry->filename, '\0', NAME_MAX); + entry->filename[0] = '.'; + + memset (entry->entryData, 0, DIR_ENTRY_DATA_SIZE); + memset (entry->entryData, ' ', 11); + entry->entryData[0] = '.'; + + entry->entryData[DIR_ENTRY_attributes] = ATTRIB_DIR; + + u16_to_u8array (entry->entryData, DIR_ENTRY_cluster, partition->rootDirCluster); + u16_to_u8array (entry->entryData, DIR_ENTRY_clusterHigh, partition->rootDirCluster >> 16); + + return true; +} + +bool _FAT_directory_getVolumeLabel (PARTITION* partition, char *label) { + DIR_ENTRY entry; + DIR_ENTRY_POSITION entryEnd; + uint8_t entryData[DIR_ENTRY_DATA_SIZE]; + int i; + bool end; + + _FAT_directory_getRootEntry(partition, &entry); + + entryEnd = entry.dataEnd; + + // Make sure we are using the correct root directory, in case of FAT32 + if (entryEnd.cluster == FAT16_ROOT_DIR_CLUSTER) { + entryEnd.cluster = partition->rootDirCluster; + } + + label[0]='\0'; + label[11]='\0'; + end = false; + //this entry should be among the first 3 entries in the root directory table, if not, then system can have trouble displaying the right volume label + while(!end) { + if(!_FAT_cache_readPartialSector (partition->cache, entryData, + _FAT_fat_clusterToSector(partition, entryEnd.cluster) + entryEnd.sector, + entryEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE)) + { //error reading + return false; + } + + if (entryData[DIR_ENTRY_attributes] == ATTRIB_VOL && entryData[0] != DIR_ENTRY_FREE) { + for (i = 0; i < 11; i++) { + label[i] = entryData[DIR_ENTRY_name + i]; + } + return true; + } else if (entryData[0] == DIR_ENTRY_LAST) { + end = true; + } + + if (_FAT_directory_incrementDirEntryPosition (partition, &entryEnd, false) == false) { + end = true; + } + } + return false; +} + +bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) { + DIR_ENTRY_POSITION entryStart = entry->dataStart; + DIR_ENTRY_POSITION entryEnd = entry->dataEnd; + bool entryStillValid; + bool finished; + ucs2_t lfn[MAX_LFN_LENGTH]; + int i; + int lfnPos; + uint8_t entryData[DIR_ENTRY_DATA_SIZE]; + + memset (entry->filename, '\0', NAME_MAX); + + // Create an empty directory entry to overwrite the old ones with + for ( entryStillValid = true, finished = false; + entryStillValid && !finished; + entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &entryStart, false)) + { + _FAT_cache_readPartialSector (partition->cache, entryData, + _FAT_fat_clusterToSector(partition, entryStart.cluster) + entryStart.sector, + entryStart.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + + if ((entryStart.cluster == entryEnd.cluster) + && (entryStart.sector == entryEnd.sector) + && (entryStart.offset == entryEnd.offset)) { + // Copy the entry data and stop, since this is the last section of the directory entry + memcpy (entry->entryData, entryData, DIR_ENTRY_DATA_SIZE); + finished = true; + } else { + // Copy the long file name data + lfnPos = ((entryData[LFN_offset_ordinal] & ~LFN_END) - 1) * 13; + for (i = 0; i < 13; i++) { + if (lfnPos + i < MAX_LFN_LENGTH - 1) { + lfn[lfnPos + i] = entryData[LFN_offset_table[i]] | (entryData[LFN_offset_table[i]+1] << 8); + } + } + } + } + + if (!entryStillValid) { + return false; + } + + entryStart = entry->dataStart; + if ((entryStart.cluster == entryEnd.cluster) + && (entryStart.sector == entryEnd.sector) + && (entryStart.offset == entryEnd.offset)) { + // Since the entry doesn't have a long file name, extract the short filename + if (!_FAT_directory_entryGetAlias (entry->entryData, entry->filename)) { + return false; + } + } else { + // Encode the long file name into a multibyte string + if (_FAT_directory_ucs2tombs (entry->filename, lfn, NAME_MAX) == (size_t)-1) { + return false; + } + } + + return true; +} + + + +bool _FAT_directory_entryFromPath (PARTITION* partition, DIR_ENTRY* entry, const char* path, const char* pathEnd) { + size_t dirnameLength; + const char* pathPosition; + const char* nextPathPosition; + uint32_t dirCluster; + bool foundFile; + char alias[MAX_ALIAS_LENGTH]; + bool found, notFound; + + pathPosition = path; + + found = false; + notFound = false; + + if (pathEnd == NULL) { + // Set pathEnd to the end of the path string + pathEnd = strchr (path, '\0'); + } + + if (pathPosition[0] == DIR_SEPARATOR) { + // Start at root directory + dirCluster = partition->rootDirCluster; + // Consume separator(s) + while (pathPosition[0] == DIR_SEPARATOR) { + pathPosition++; + } + // If the path is only specifying a directory in the form of "/" return it + if (pathPosition >= pathEnd) { + _FAT_directory_getRootEntry (partition, entry); + found = true; + } + } else { + // Start in current working directory + dirCluster = partition->cwdCluster; + } + + while (!found && !notFound) { + // Get the name of the next required subdirectory within the path + nextPathPosition = strchr (pathPosition, DIR_SEPARATOR); + if (nextPathPosition != NULL) { + dirnameLength = nextPathPosition - pathPosition; + } else { + dirnameLength = strlen(pathPosition); + } + + if (dirnameLength > NAME_MAX) { + // The path is too long to bother with + return false; + } + + // Check for "." or ".." when the dirCluster is root cluster + // These entries do not exist, so we must fake it + if ((dirCluster == partition->rootDirCluster) + && ((strncmp(".", pathPosition, dirnameLength) == 0) + || (strncmp("..", pathPosition, dirnameLength) == 0))) { + foundFile = true; + _FAT_directory_getRootEntry(partition, entry); + } else { + // Look for the directory within the path + foundFile = _FAT_directory_getFirstEntry (partition, entry, dirCluster); + + while (foundFile && !found && !notFound) { // It hasn't already found the file + // Check if the filename matches + if ((dirnameLength == strnlen(entry->filename, NAME_MAX)) + && (_FAT_directory_mbsncasecmp(pathPosition, entry->filename, dirnameLength) == 0)) { + found = true; + } + + // Check if the alias matches + _FAT_directory_entryGetAlias (entry->entryData, alias); + if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH)) + && (strncasecmp(pathPosition, alias, dirnameLength) == 0)) { + found = true; + } + + if (found && !(entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) && (nextPathPosition != NULL)) { + // Make sure that we aren't trying to follow a file instead of a directory in the path + found = false; + } + + if (!found) { + foundFile = _FAT_directory_getNextEntry (partition, entry); + } + } + } + + if (!foundFile) { + // Check that the search didn't get to the end of the directory + notFound = true; + found = false; + } else if ((nextPathPosition == NULL) || (nextPathPosition >= pathEnd)) { + // Check that we reached the end of the path + found = true; + } else if (entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) { + dirCluster = _FAT_directory_entryGetCluster (partition, entry->entryData); + if (dirCluster == CLUSTER_ROOT) + dirCluster = partition->rootDirCluster; + pathPosition = nextPathPosition; + // Consume separator(s) + while (pathPosition[0] == DIR_SEPARATOR) { + pathPosition++; + } + // The requested directory was found + if (pathPosition >= pathEnd) { + found = true; + } else { + found = false; + } + } + } + + if (found && !notFound) { + if (partition->filesysType == FS_FAT32 && (entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) && + _FAT_directory_entryGetCluster (partition, entry->entryData) == CLUSTER_ROOT) + { + // On FAT32 it should specify an actual cluster for the root entry, + // not cluster 0 as on FAT16 + _FAT_directory_getRootEntry (partition, entry); + } + return true; + } else { + return false; + } +} + +bool _FAT_directory_removeEntry (PARTITION* partition, DIR_ENTRY* entry) { + DIR_ENTRY_POSITION entryStart = entry->dataStart; + DIR_ENTRY_POSITION entryEnd = entry->dataEnd; + bool entryStillValid; + bool finished; + uint8_t entryData[DIR_ENTRY_DATA_SIZE]; + + // Create an empty directory entry to overwrite the old ones with + for ( entryStillValid = true, finished = false; + entryStillValid && !finished; + entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &entryStart, false)) + { + _FAT_cache_readPartialSector (partition->cache, entryData, _FAT_fat_clusterToSector(partition, entryStart.cluster) + entryStart.sector, entryStart.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + entryData[0] = DIR_ENTRY_FREE; + _FAT_cache_writePartialSector (partition->cache, entryData, _FAT_fat_clusterToSector(partition, entryStart.cluster) + entryStart.sector, entryStart.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + if ((entryStart.cluster == entryEnd.cluster) && (entryStart.sector == entryEnd.sector) && (entryStart.offset == entryEnd.offset)) { + finished = true; + } + } + + if (!entryStillValid) { + return false; + } + + return true; +} + +static bool _FAT_directory_findEntryGap (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster, size_t size) { + DIR_ENTRY_POSITION gapStart; + DIR_ENTRY_POSITION gapEnd; + uint8_t entryData[DIR_ENTRY_DATA_SIZE]; + size_t dirEntryRemain; + bool endOfDirectory, entryStillValid; + + // Scan Dir for free entry + gapEnd.offset = 0; + gapEnd.sector = 0; + gapEnd.cluster = dirCluster; + + gapStart = gapEnd; + + entryStillValid = true; + dirEntryRemain = size; + endOfDirectory = false; + + while (entryStillValid && !endOfDirectory && (dirEntryRemain > 0)) { + _FAT_cache_readPartialSector (partition->cache, entryData, + _FAT_fat_clusterToSector(partition, gapEnd.cluster) + gapEnd.sector, + gapEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + if (entryData[0] == DIR_ENTRY_LAST) { + if (dirEntryRemain == size) { + gapStart = gapEnd; + } + -- dirEntryRemain; + endOfDirectory = true; + } else if (entryData[0] == DIR_ENTRY_FREE) { + if (dirEntryRemain == size) { + gapStart = gapEnd; + } + -- dirEntryRemain; + } else { + dirEntryRemain = size; + } + + if (!endOfDirectory && (dirEntryRemain > 0)) { + entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &gapEnd, true); + } + } + + // Make sure the scanning didn't fail + if (!entryStillValid) { + return false; + } + + // Save the start entry, since we know it is valid + entry->dataStart = gapStart; + + if (endOfDirectory) { + memset (entryData, DIR_ENTRY_LAST, DIR_ENTRY_DATA_SIZE); + dirEntryRemain += 1; // Increase by one to take account of End Of Directory Marker + while ((dirEntryRemain > 0) && entryStillValid) { + // Get the gapEnd before incrementing it, so the second to last one is saved + entry->dataEnd = gapEnd; + // Increment gapEnd, moving onto the next entry + entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &gapEnd, true); + -- dirEntryRemain; + // Fill the entry with blanks + _FAT_cache_writePartialSector (partition->cache, entryData, + _FAT_fat_clusterToSector(partition, gapEnd.cluster) + gapEnd.sector, + gapEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + } + if (!entryStillValid) { + return false; + } + } else { + entry->dataEnd = gapEnd; + } + + return true; +} + +static bool _FAT_directory_entryExists (PARTITION* partition, const char* name, uint32_t dirCluster) { + DIR_ENTRY tempEntry; + bool foundFile; + char alias[MAX_ALIAS_LENGTH]; + size_t dirnameLength; + + dirnameLength = strnlen(name, NAME_MAX); + + if (dirnameLength >= NAME_MAX) { + return false; + } + + // Make sure the entry doesn't already exist + foundFile = _FAT_directory_getFirstEntry (partition, &tempEntry, dirCluster); + + while (foundFile) { // It hasn't already found the file + // Check if the filename matches + if ((dirnameLength == strnlen(tempEntry.filename, NAME_MAX)) + && (_FAT_directory_mbsncasecmp(name, tempEntry.filename, dirnameLength) == 0)) { + return true; + } + + // Check if the alias matches + _FAT_directory_entryGetAlias (tempEntry.entryData, alias); + if ((strncasecmp(name, alias, MAX_ALIAS_LENGTH) == 0)) { + return true; + } + foundFile = _FAT_directory_getNextEntry (partition, &tempEntry); + } + return false; +} + +/* +Creates an alias for a long file name. If the alias is not an exact match for the +filename, it returns the number of characters in the alias. If the two names match, +it returns 0. If there was an error, it returns -1. +*/ +static int _FAT_directory_createAlias (char* alias, const char* lfn) { + bool lossyConversion = false; // Set when the alias had to be modified to be valid + int lfnPos = 0; + int aliasPos = 0; + wchar_t lfnChar; + int oemChar; + mbstate_t ps = {0}; + int bytesUsed = 0; + const char* lfnExt; + int aliasExtLen; + + // Strip leading periods + while (lfn[lfnPos] == '.') { + lfnPos ++; + lossyConversion = true; + } + + // Primary portion of alias + while (aliasPos < 8 && lfn[lfnPos] != '.' && lfn[lfnPos] != '\0') { + bytesUsed = mbrtowc(&lfnChar, lfn + lfnPos, NAME_MAX - lfnPos, &ps); + if (bytesUsed < 0) { + return -1; + } + oemChar = wctob(towupper((wint_t)lfnChar)); + if (wctob((wint_t)lfnChar) != oemChar) { + // Case of letter was changed + lossyConversion = true; + } + if (oemChar == ' ') { + // Skip spaces in filename + lossyConversion = true; + lfnPos += bytesUsed; + continue; + } + if (oemChar == EOF) { + oemChar = '_'; // Replace unconvertable characters with underscores + lossyConversion = true; + } + if (strchr (ILLEGAL_ALIAS_CHARACTERS, oemChar) != NULL) { + // Invalid Alias character + oemChar = '_'; // Replace illegal characters with underscores + lossyConversion = true; + } + + alias[aliasPos] = (char)oemChar; + aliasPos++; + lfnPos += bytesUsed; + } + + if (lfn[lfnPos] != '.' && lfn[lfnPos] != '\0') { + // Name was more than 8 characters long + lossyConversion = true; + } + + // Alias extension + lfnExt = strrchr (lfn, '.'); + if (lfnExt != NULL && lfnExt != strchr (lfn, '.')) { + // More than one period in name + lossyConversion = true; + } + if (lfnExt != NULL && lfnExt[1] != '\0') { + lfnExt++; + alias[aliasPos] = '.'; + aliasPos++; + memset (&ps, 0, sizeof(ps)); + for (aliasExtLen = 0; aliasExtLen < MAX_ALIAS_EXT_LENGTH && *lfnExt != '\0'; aliasExtLen++) { + bytesUsed = mbrtowc(&lfnChar, lfnExt, NAME_MAX - lfnPos, &ps); + if (bytesUsed < 0) { + return -1; + } + oemChar = wctob(towupper((wint_t)lfnChar)); + if (wctob((wint_t)lfnChar) != oemChar) { + // Case of letter was changed + lossyConversion = true; + } + if (oemChar == ' ') { + // Skip spaces in alias + lossyConversion = true; + lfnExt += bytesUsed; + continue; + } + if (oemChar == EOF) { + oemChar = '_'; // Replace unconvertable characters with underscores + lossyConversion = true; + } + if (strchr (ILLEGAL_ALIAS_CHARACTERS, oemChar) != NULL) { + // Invalid Alias character + oemChar = '_'; // Replace illegal characters with underscores + lossyConversion = true; + } + + alias[aliasPos] = (char)oemChar; + aliasPos++; + lfnExt += bytesUsed; + } + if (*lfnExt != '\0') { + // Extension was more than 3 characters long + lossyConversion = true; + } + } + + alias[aliasPos] = '\0'; + if (lossyConversion) { + return aliasPos; + } else { + return 0; + } +} + +bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster) { + size_t entrySize; + uint8_t lfnEntry[DIR_ENTRY_DATA_SIZE]; + int i,j; // Must be signed for use when decrementing in for loop + char *tmpCharPtr; + DIR_ENTRY_POSITION curEntryPos; + bool entryStillValid; + uint8_t aliasCheckSum = 0; + char alias [MAX_ALIAS_LENGTH]; + int aliasLen; + int lfnLen; + + // Remove trailing spaces + for (i = strlen (entry->filename) - 1; (i >= 0) && (entry->filename[i] == ' '); --i) { + entry->filename[i] = '\0'; + } +#if 0 + // Remove leading spaces + for (i = 0; entry->filename[i] == ' '; ++i) ; + if (i > 0) { + memmove (entry->filename, entry->filename + i, strlen (entry->filename + i)); + } +#endif + + // Make sure the filename is not 0 length + if (strnlen (entry->filename, NAME_MAX) < 1) { + return false; + } + + // Make sure the filename is at least a valid LFN + lfnLen = _FAT_directory_lfnLength (entry->filename); + if (lfnLen < 0) { + return false; + } + + // Remove junk in filename + i = strlen (entry->filename); + memset (entry->filename + i, '\0', NAME_MAX - i); + + // Make sure the entry doesn't already exist + if (_FAT_directory_entryExists (partition, entry->filename, dirCluster)) { + return false; + } + + // Clear out alias, so we can generate a new one + memset (entry->entryData, ' ', 11); + + if ( strncmp(entry->filename, ".", NAME_MAX) == 0) { + // "." entry + entry->entryData[0] = '.'; + entrySize = 1; + } else if ( strncmp(entry->filename, "..", NAME_MAX) == 0) { + // ".." entry + entry->entryData[0] = '.'; + entry->entryData[1] = '.'; + entrySize = 1; + } else { + // Normal file name + aliasLen = _FAT_directory_createAlias (alias, entry->filename); + if (aliasLen < 0) { + return false; + } else if (aliasLen == 0) { + // It's a normal short filename + entrySize = 1; + } else { + // It's a long filename with an alias + entrySize = ((lfnLen + LFN_ENTRY_LENGTH - 1) / LFN_ENTRY_LENGTH) + 1; + + // Generate full alias for all cases except when the alias is simply an upper case version of the LFN + // and there isn't already a file with that name + if (strncasecmp (alias, entry->filename, MAX_ALIAS_LENGTH) != 0 || + _FAT_directory_entryExists (partition, alias, dirCluster)) + { + // expand primary part to 8 characters long by padding the end with underscores + i = 0; + j = MAX_ALIAS_PRI_LENGTH; + // Move extension to last 3 characters + while (alias[i] != '.' && alias[i] != '\0') i++; + if (i < j) { + memmove (alias + j, alias + i, aliasLen - i + 1); + // Pad primary component + memset (alias + i, '_', j - i); + } + // Generate numeric tail + for (i = 1; i <= MAX_NUMERIC_TAIL; i++) { + j = i; + tmpCharPtr = alias + MAX_ALIAS_PRI_LENGTH - 1; + while (j > 0) { + *tmpCharPtr = '0' + (j % 10); // ASCII numeric value + tmpCharPtr--; + j /= 10; + } + *tmpCharPtr = '~'; + if (!_FAT_directory_entryExists (partition, alias, dirCluster)) { + break; + } + } + if (i > MAX_NUMERIC_TAIL) { + // Couldn't get a valid alias + return false; + } + } + } + + // Copy alias or short file name into directory entry data + for (i = 0, j = 0; (j < 8) && (alias[i] != '.') && (alias[i] != '\0'); i++, j++) { + entry->entryData[j] = alias[i]; + } + while (j < 8) { + entry->entryData[j] = ' '; + ++ j; + } + if (alias[i] == '.') { + // Copy extension + ++ i; + while ((alias[i] != '\0') && (j < 11)) { + entry->entryData[j] = alias[i]; + ++ i; + ++ j; + } + } + while (j < 11) { + entry->entryData[j] = ' '; + ++ j; + } + + // Generate alias checksum + for (i=0; i < ALIAS_ENTRY_LENGTH; i++) { + // NOTE: The operation is an unsigned char rotate right + aliasCheckSum = ((aliasCheckSum & 1) ? 0x80 : 0) + (aliasCheckSum >> 1) + entry->entryData[i]; + } + } + + // Find or create space for the entry + if (_FAT_directory_findEntryGap (partition, entry, dirCluster, entrySize) == false) { + return false; + } + + // Write out directory entry + curEntryPos = entry->dataStart; + + { + // lfn is only pushed onto the stack here, reducing overall stack usage + ucs2_t lfn[MAX_LFN_LENGTH] = {0}; + _FAT_directory_mbstoucs2 (lfn, entry->filename, MAX_LFN_LENGTH); + + for (entryStillValid = true, i = entrySize; entryStillValid && i > 0; + entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &curEntryPos, false), -- i ) + { + if (i > 1) { + // Long filename entry + lfnEntry[LFN_offset_ordinal] = (i - 1) | ((size_t)i == entrySize ? LFN_END : 0); + for (j = 0; j < 13; j++) { + if (lfn [(i - 2) * 13 + j] == '\0') { + if ((j > 1) && (lfn [(i - 2) * 13 + (j-1)] == '\0')) { + u16_to_u8array (lfnEntry, LFN_offset_table[j], 0xffff); // Padding + } else { + u16_to_u8array (lfnEntry, LFN_offset_table[j], 0x0000); // Terminating null character + } + } else { + u16_to_u8array (lfnEntry, LFN_offset_table[j], lfn [(i - 2) * 13 + j]); + } + } + + lfnEntry[LFN_offset_checkSum] = aliasCheckSum; + lfnEntry[LFN_offset_flag] = ATTRIB_LFN; + lfnEntry[LFN_offset_reserved1] = 0; + u16_to_u8array (lfnEntry, LFN_offset_reserved2, 0); + _FAT_cache_writePartialSector (partition->cache, lfnEntry, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + } else { + // Alias & file data + _FAT_cache_writePartialSector (partition->cache, entry->entryData, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + } + } + } + + return true; +} + +bool _FAT_directory_chdir (PARTITION* partition, const char* path) { + DIR_ENTRY entry; + + if (!_FAT_directory_entryFromPath (partition, &entry, path, NULL)) { + return false; + } + + if (!(entry.entryData[DIR_ENTRY_attributes] & ATTRIB_DIR)) { + return false; + } + + partition->cwdCluster = _FAT_directory_entryGetCluster (partition, entry.entryData); + + return true; +} + +void _FAT_directory_entryStat (PARTITION* partition, DIR_ENTRY* entry, struct stat *st) { + // Fill in the stat struct + // Some of the values are faked for the sake of compatibility + st->st_dev = _FAT_disc_hostType(partition->disc); // The device is the 32bit ioType value + st->st_ino = (ino_t)(_FAT_directory_entryGetCluster(partition, entry->entryData)); // The file serial number is the start cluster + st->st_mode = (_FAT_directory_isDirectory(entry) ? S_IFDIR : S_IFREG) | + (S_IRUSR | S_IRGRP | S_IROTH) | + (_FAT_directory_isWritable (entry) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0); // Mode bits based on dirEntry ATTRIB byte + st->st_nlink = 1; // Always one hard link on a FAT file + st->st_uid = 1; // Faked for FAT + st->st_gid = 2; // Faked for FAT + st->st_rdev = st->st_dev; + st->st_size = u8array_to_u32 (entry->entryData, DIR_ENTRY_fileSize); // File size + st->st_atime = _FAT_filetime_to_time_t ( + 0, + u8array_to_u16 (entry->entryData, DIR_ENTRY_aDate) + ); + st->st_mtime = _FAT_filetime_to_time_t ( + u8array_to_u16 (entry->entryData, DIR_ENTRY_mTime), + u8array_to_u16 (entry->entryData, DIR_ENTRY_mDate) + ); + st->st_ctime = _FAT_filetime_to_time_t ( + u8array_to_u16 (entry->entryData, DIR_ENTRY_cTime), + u8array_to_u16 (entry->entryData, DIR_ENTRY_cDate) + ); + st->st_blksize = partition->bytesPerSector; // Prefered file I/O block size + st->st_blocks = (st->st_size + partition->bytesPerSector - 1) / partition->bytesPerSector; // File size in blocks +} diff --git a/arm9/src/lib/libfat/source/directory.h b/arm9/src/lib/libfat/source/directory.h new file mode 100644 index 0000000..9f7af8e --- /dev/null +++ b/arm9/src/lib/libfat/source/directory.h @@ -0,0 +1,181 @@ +/* + directory.h + Reading, writing and manipulation of the directory structure on + a FAT partition + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _DIRECTORY_H +#define _DIRECTORY_H + +#include +#include + +#include "common.h" +#include "partition.h" + +#define DIR_ENTRY_DATA_SIZE 0x20 +#define MAX_LFN_LENGTH 256 +#define MAX_ALIAS_LENGTH 13 +#define LFN_ENTRY_LENGTH 13 +#define ALIAS_ENTRY_LENGTH 11 +#define MAX_ALIAS_EXT_LENGTH 3 +#define MAX_ALIAS_PRI_LENGTH 8 +#define MAX_NUMERIC_TAIL 999999 +#define FAT16_ROOT_DIR_CLUSTER 0 + +#define DIR_SEPARATOR '/' + +// File attributes +#define ATTRIB_ARCH 0x20 // Archive +#define ATTRIB_DIR 0x10 // Directory +#define ATTRIB_LFN 0x0F // Long file name +#define ATTRIB_VOL 0x08 // Volume +#define ATTRIB_SYS 0x04 // System +#define ATTRIB_HID 0x02 // Hidden +#define ATTRIB_RO 0x01 // Read only + +#define CASE_LOWER_EXT 0x10 // WinNT lowercase extension +#define CASE_LOWER_BASE 0x08 // WinNT lowercase basename + +typedef enum {FT_DIRECTORY, FT_FILE} FILE_TYPE; + +typedef struct { + uint32_t cluster; + sec_t sector; + int32_t offset; +} DIR_ENTRY_POSITION; + +typedef struct { + uint8_t entryData[DIR_ENTRY_DATA_SIZE]; + DIR_ENTRY_POSITION dataStart; // Points to the start of the LFN entries of a file, or the alias for no LFN + DIR_ENTRY_POSITION dataEnd; // Always points to the file/directory's alias entry + char filename[NAME_MAX]; +} DIR_ENTRY; + +// Directory entry offsets +enum DIR_ENTRY_offset { + DIR_ENTRY_name = 0x00, + DIR_ENTRY_extension = 0x08, + DIR_ENTRY_attributes = 0x0B, + DIR_ENTRY_caseInfo = 0x0C, + DIR_ENTRY_cTime_ms = 0x0D, + DIR_ENTRY_cTime = 0x0E, + DIR_ENTRY_cDate = 0x10, + DIR_ENTRY_aDate = 0x12, + DIR_ENTRY_clusterHigh = 0x14, + DIR_ENTRY_mTime = 0x16, + DIR_ENTRY_mDate = 0x18, + DIR_ENTRY_cluster = 0x1A, + DIR_ENTRY_fileSize = 0x1C +}; + +/* +Returns true if the file specified by entry is a directory +*/ +static inline bool _FAT_directory_isDirectory (DIR_ENTRY* entry) { + return ((entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) != 0); +} + +static inline bool _FAT_directory_isWritable (DIR_ENTRY* entry) { + return ((entry->entryData[DIR_ENTRY_attributes] & ATTRIB_RO) == 0); +} + +static inline bool _FAT_directory_isDot (DIR_ENTRY* entry) { + return ((entry->filename[0] == '.') && ((entry->filename[1] == '\0') || + ((entry->filename[1] == '.') && entry->filename[2] == '\0'))); +} + +/* +Reads the first directory entry from the directory starting at dirCluster +Places result in entry +entry will be destroyed even if no directory entry is found +Returns true on success, false on failure +*/ +bool _FAT_directory_getFirstEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster); + +/* +Reads the next directory entry after the one already pointed to by entry +Places result in entry +entry will be destroyed even if no directory entry is found +Returns true on success, false on failure +*/ +bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry); + +/* +Gets the directory entry corrsponding to the supplied path +entry will be destroyed even if no directory entry is found +pathEnd specifies the end of the path string, for cutting strings short if needed + specify NULL to use the full length of path + pathEnd is only a suggestion, and the path string will be searched up until the next PATH_SEPARATOR + after pathEND. +Returns true on success, false on failure +*/ +bool _FAT_directory_entryFromPath (PARTITION* partition, DIR_ENTRY* entry, const char* path, const char* pathEnd); + +/* +Changes the current directory to the one specified by path +Returns true on success, false on failure +*/ +bool _FAT_directory_chdir (PARTITION* partition, const char* path); + +/* +Removes the directory entry specified by entry +Assumes that entry is valid +Returns true on success, false on failure +*/ +bool _FAT_directory_removeEntry (PARTITION* partition, DIR_ENTRY* entry); + +/* +Add a directory entry to the directory specified by dirCluster +The fileData, dataStart and dataEnd elements of the DIR_ENTRY struct are +updated with the new directory entry position and alias. +Returns true on success, false on failure +*/ +bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster); + +/* +Get the start cluster of a file from it's entry data +*/ +uint32_t _FAT_directory_entryGetCluster (PARTITION* partition, const uint8_t* entryData); + +/* +Fill in the file name and entry data of DIR_ENTRY* entry. +Assumes that the entry's dataStart and dataEnd are correct +Returns true on success, false on failure +*/ +bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry); + +/* +Fill in a stat struct based on a file entry +*/ +void _FAT_directory_entryStat (PARTITION* partition, DIR_ENTRY* entry, struct stat *st); + +/* +Get volume label +*/ +bool _FAT_directory_getVolumeLabel (PARTITION* partition, char *label); + +#endif // _DIRECTORY_H diff --git a/arm9/src/lib/libfat/source/disc.c b/arm9/src/lib/libfat/source/disc.c new file mode 100755 index 0000000..f923fd0 --- /dev/null +++ b/arm9/src/lib/libfat/source/disc.c @@ -0,0 +1,125 @@ +/* + disc.c + Interface to the low level disc functions. Used by the higher level + file system code. + + Copyright (c) 2008 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "disc.h" + +/* +The list of interfaces consists of a series of name/interface pairs. +The interface is returned via a simple function. This allows for +platforms where the interface has to be "assembled" before it can +be used, like DLDI on the NDS. For cases where a simple struct +is available, wrapper functions are used. +The list is terminated by a NULL/NULL entry. +*/ + +/* ====================== Wii ====================== */ +#if defined (__wii__) +#include +#include +#include + +static const DISC_INTERFACE* get_io_wiisd (void) { + return &__io_wiisd; +} +static const DISC_INTERFACE* get_io_usbstorage (void) { + return &__io_usbstorage; +} + +static const DISC_INTERFACE* get_io_gcsda (void) { + return &__io_gcsda; +} + +static const DISC_INTERFACE* get_io_gcsdb (void) { + return &__io_gcsdb; +} + +const INTERFACE_ID _FAT_disc_interfaces[] = { + {"sd", get_io_wiisd}, + {"usb", get_io_usbstorage}, + {"carda", get_io_gcsda}, + {"cardb", get_io_gcsdb}, + {NULL, NULL} +}; + +/* ==================== Gamecube ==================== */ +#elif defined (__gamecube__) +#include + +static const DISC_INTERFACE* get_io_gcsd2 (void) { + return &__io_gcsd2; +} + +static const DISC_INTERFACE* get_io_gcsdb (void) { + return &__io_gcsdb; +} + +static const DISC_INTERFACE* get_io_gcsda (void) { + return &__io_gcsda; +} + +const INTERFACE_ID _FAT_disc_interfaces[] = { + {"sd", get_io_gcsd2}, + {"carda", get_io_gcsda}, + {"cardb", get_io_gcsdb}, + {NULL, NULL} +}; + +/* ====================== NDS ====================== */ +#elif defined (NDS) +#include +#include +#include + +const INTERFACE_ID _FAT_disc_interfaces[] = { + {"sd", get_io_dsisd}, + {"fat", dldiGetInternal}, + {NULL, NULL} +}; + +/* ====================== GBA ====================== */ +#elif defined (GBA) +#include + +const INTERFACE_ID _FAT_disc_interfaces[] = { + {"fat", discGetInterface}, + {NULL, NULL} +}; + +/* ====================== GP2X ====================== */ +#elif defined (GP2X) +#include + +const INTERFACE_ID _FAT_disc_interfaces[] = { + {"sd", get_io_gp2xsd}, + {NULL, NULL} + +}; + +#endif + diff --git a/arm9/src/lib/libfat/source/disc.h b/arm9/src/lib/libfat/source/disc.h new file mode 100644 index 0000000..5c955f9 --- /dev/null +++ b/arm9/src/lib/libfat/source/disc.h @@ -0,0 +1,110 @@ +/* + disc.h + Interface to the low level disc functions. Used by the higher level + file system code. + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef _DISC_H +#define _DISC_H + +#include "common.h" + +/* +A list of all default devices to try at startup, +terminated by a {NULL,NULL} entry. +*/ +typedef struct { + const char* name; + const DISC_INTERFACE* (*getInterface)(void); +} INTERFACE_ID; +extern const INTERFACE_ID _FAT_disc_interfaces[]; + +/* +Check if a disc is inserted +Return true if a disc is inserted and ready, false otherwise +*/ +static inline bool _FAT_disc_isInserted (const DISC_INTERFACE* disc) { + return disc->isInserted(); +} + +/* +Read numSectors sectors from a disc, starting at sector. +numSectors is between 1 and LIMIT_SECTORS if LIMIT_SECTORS is defined, +else it is at least 1 +sector is 0 or greater +buffer is a pointer to the memory to fill +*/ +static inline bool _FAT_disc_readSectors (const DISC_INTERFACE* disc, sec_t sector, sec_t numSectors, void* buffer) { + return disc->readSectors (sector, numSectors, buffer); +} + +/* +Write numSectors sectors to a disc, starting at sector. +numSectors is between 1 and LIMIT_SECTORS if LIMIT_SECTORS is defined, +else it is at least 1 +sector is 0 or greater +buffer is a pointer to the memory to read from +*/ +static inline bool _FAT_disc_writeSectors (const DISC_INTERFACE* disc, sec_t sector, sec_t numSectors, const void* buffer) { + return disc->writeSectors (sector, numSectors, buffer); +} + +/* +Reset the card back to a ready state +*/ +static inline bool _FAT_disc_clearStatus (const DISC_INTERFACE* disc) { + return disc->clearStatus(); +} + +/* +Initialise the disc to a state ready for data reading or writing +*/ +static inline bool _FAT_disc_startup (const DISC_INTERFACE* disc) { + return disc->startup(); +} + +/* +Put the disc in a state ready for power down. +Complete any pending writes and disable the disc if necessary +*/ +static inline bool _FAT_disc_shutdown (const DISC_INTERFACE* disc) { + return disc->shutdown(); +} + +/* +Return a 32 bit value unique to each type of interface +*/ +static inline uint32_t _FAT_disc_hostType (const DISC_INTERFACE* disc) { + return disc->ioType; +} + +/* +Return a 32 bit value that specifies the capabilities of the disc +*/ +static inline uint32_t _FAT_disc_features (const DISC_INTERFACE* disc) { + return disc->features; +} + +#endif // _DISC_H diff --git a/arm9/src/lib/libfat/source/fatdir.c b/arm9/src/lib/libfat/source/fatdir.c new file mode 100644 index 0000000..edb8141 --- /dev/null +++ b/arm9/src/lib/libfat/source/fatdir.c @@ -0,0 +1,633 @@ +/* + fatdir.c + + Functions used by the newlib disc stubs to interface with + this library + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include "fatdir.h" + +#include "cache.h" +#include "file_allocation_table.h" +#include "partition.h" +#include "directory.h" +#include "bit_ops.h" +#include "filetime.h" +#include "lock.h" + + +int _FAT_stat_r (struct _reent *r, const char *path, struct stat *st) { + PARTITION* partition = NULL; + DIR_ENTRY dirEntry; + + // Get the partition this file is on + partition = _FAT_partition_getPartitionFromPath (path); + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + r->_errno = EINVAL; + return -1; + } + + _FAT_lock(&partition->lock); + + // Search for the file on the disc + if (!_FAT_directory_entryFromPath (partition, &dirEntry, path, NULL)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOENT; + return -1; + } + + // Fill in the stat struct + _FAT_directory_entryStat (partition, &dirEntry, st); + + _FAT_unlock(&partition->lock); + return 0; +} + +int _FAT_link_r (struct _reent *r, const char *existing, const char *newLink) { + r->_errno = ENOTSUP; + return -1; +} + +static int _FAT_unlinkCommon (struct _reent *r, const char *path, bool isRmDir) { + PARTITION* partition = NULL; + DIR_ENTRY dirEntry; + DIR_ENTRY dirContents; + uint32_t cluster; + bool nextEntry; + bool errorOccured = false; + + // Get the partition this directory is on + partition = _FAT_partition_getPartitionFromPath (path); + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + // Make sure we aren't trying to write to a read-only disc + if (partition->readOnly) { + r->_errno = EROFS; + return -1; + } + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + r->_errno = EINVAL; + return -1; + } + + _FAT_lock(&partition->lock); + + // Search for the file on the disc + if (!_FAT_directory_entryFromPath (partition, &dirEntry, path, NULL)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOENT; + return -1; + } + + cluster = _FAT_directory_entryGetCluster (partition, dirEntry.entryData); + + + // If this is a directory, make sure it is empty + if (_FAT_directory_isDirectory (&dirEntry)) { + if (!isRmDir) { + _FAT_unlock(&partition->lock); + r->_errno = EISDIR; + return -1; + } + + nextEntry = _FAT_directory_getFirstEntry (partition, &dirContents, cluster); + + while (nextEntry) { + if (!_FAT_directory_isDot (&dirContents)) { + // The directory had something in it that isn't a reference to itself or it's parent + _FAT_unlock(&partition->lock); + r->_errno = ENOTEMPTY; + return -1; + } + nextEntry = _FAT_directory_getNextEntry (partition, &dirContents); + } + } else if (isRmDir) { + _FAT_unlock(&partition->lock); + r->_errno = ENOTDIR; + return -1; + } + + if (_FAT_fat_isValidCluster(partition, cluster)) { + // Remove the cluster chain for this file + if (!_FAT_fat_clearLinks (partition, cluster)) { + r->_errno = EIO; + errorOccured = true; + } + } + + // Remove the directory entry for this file + if (!_FAT_directory_removeEntry (partition, &dirEntry)) { + r->_errno = EIO; + errorOccured = true; + } + + // Flush any sectors in the disc cache + if (!_FAT_cache_flush(partition->cache)) { + r->_errno = EIO; + errorOccured = true; + } + + _FAT_unlock(&partition->lock); + if (errorOccured) { + return -1; + } else { + return 0; + } +} + +int _FAT_unlink_r (struct _reent *r, const char *path) { + return _FAT_unlinkCommon (r, path, false); +} + +int _FAT_chdir_r (struct _reent *r, const char *path) { + PARTITION* partition = NULL; + + // Get the partition this directory is on + partition = _FAT_partition_getPartitionFromPath (path); + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + r->_errno = EINVAL; + return -1; + } + + _FAT_lock(&partition->lock); + + // Try changing directory + if (_FAT_directory_chdir (partition, path)) { + // Successful + _FAT_unlock(&partition->lock); + return 0; + } else { + // Failed + _FAT_unlock(&partition->lock); + r->_errno = ENOTDIR; + return -1; + } +} + +int _FAT_rename_r (struct _reent *r, const char *oldName, const char *newName) { + PARTITION* partition = NULL; + DIR_ENTRY oldDirEntry; + DIR_ENTRY newDirEntry; + const char *pathEnd; + uint32_t dirCluster; + + // Get the partition this directory is on + partition = _FAT_partition_getPartitionFromPath (oldName); + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + _FAT_lock(&partition->lock); + + // Make sure the same partition is used for the old and new names + if (partition != _FAT_partition_getPartitionFromPath (newName)) { + _FAT_unlock(&partition->lock); + r->_errno = EXDEV; + return -1; + } + + // Make sure we aren't trying to write to a read-only disc + if (partition->readOnly) { + _FAT_unlock(&partition->lock); + r->_errno = EROFS; + return -1; + } + + // Move the path pointer to the start of the actual path + if (strchr (oldName, ':') != NULL) { + oldName = strchr (oldName, ':') + 1; + } + if (strchr (oldName, ':') != NULL) { + _FAT_unlock(&partition->lock); + r->_errno = EINVAL; + return -1; + } + if (strchr (newName, ':') != NULL) { + newName = strchr (newName, ':') + 1; + } + if (strchr (newName, ':') != NULL) { + _FAT_unlock(&partition->lock); + r->_errno = EINVAL; + return -1; + } + + // Search for the file on the disc + if (!_FAT_directory_entryFromPath (partition, &oldDirEntry, oldName, NULL)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOENT; + return -1; + } + + // Make sure there is no existing file / directory with the new name + if (_FAT_directory_entryFromPath (partition, &newDirEntry, newName, NULL)) { + _FAT_unlock(&partition->lock); + r->_errno = EEXIST; + return -1; + } + + // Create the new file entry + // Get the directory it has to go in + pathEnd = strrchr (newName, DIR_SEPARATOR); + if (pathEnd == NULL) { + // No path was specified + dirCluster = partition->cwdCluster; + pathEnd = newName; + } else { + // Path was specified -- get the right dirCluster + // Recycling newDirEntry, since it needs to be recreated anyway + if (!_FAT_directory_entryFromPath (partition, &newDirEntry, newName, pathEnd) || + !_FAT_directory_isDirectory(&newDirEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOTDIR; + return -1; + } + dirCluster = _FAT_directory_entryGetCluster (partition, newDirEntry.entryData); + // Move the pathEnd past the last DIR_SEPARATOR + pathEnd += 1; + } + + // Copy the entry data + memcpy (&newDirEntry, &oldDirEntry, sizeof(DIR_ENTRY)); + + // Set the new name + strncpy (newDirEntry.filename, pathEnd, NAME_MAX - 1); + + // Write the new entry + if (!_FAT_directory_addEntry (partition, &newDirEntry, dirCluster)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOSPC; + return -1; + } + + // Remove the old entry + if (!_FAT_directory_removeEntry (partition, &oldDirEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = EIO; + return -1; + } + + // Flush any sectors in the disc cache + if (!_FAT_cache_flush (partition->cache)) { + _FAT_unlock(&partition->lock); + r->_errno = EIO; + return -1; + } + + _FAT_unlock(&partition->lock); + return 0; +} + +int _FAT_mkdir_r (struct _reent *r, const char *path, int mode) { + PARTITION* partition = NULL; + bool fileExists; + DIR_ENTRY dirEntry; + const char* pathEnd; + uint32_t parentCluster, dirCluster; + uint8_t newEntryData[DIR_ENTRY_DATA_SIZE]; + + partition = _FAT_partition_getPartitionFromPath (path); + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + r->_errno = EINVAL; + return -1; + } + + _FAT_lock(&partition->lock); + + // Search for the file/directory on the disc + fileExists = _FAT_directory_entryFromPath (partition, &dirEntry, path, NULL); + + // Make sure it doesn't exist + if (fileExists) { + _FAT_unlock(&partition->lock); + r->_errno = EEXIST; + return -1; + } + + if (partition->readOnly) { + // We can't write to a read-only partition + _FAT_unlock(&partition->lock); + r->_errno = EROFS; + return -1; + } + + // Get the directory it has to go in + pathEnd = strrchr (path, DIR_SEPARATOR); + if (pathEnd == NULL) { + // No path was specified + parentCluster = partition->cwdCluster; + pathEnd = path; + } else { + // Path was specified -- get the right parentCluster + // Recycling dirEntry, since it needs to be recreated anyway + if (!_FAT_directory_entryFromPath (partition, &dirEntry, path, pathEnd) || + !_FAT_directory_isDirectory(&dirEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOTDIR; + return -1; + } + parentCluster = _FAT_directory_entryGetCluster (partition, dirEntry.entryData); + // Move the pathEnd past the last DIR_SEPARATOR + pathEnd += 1; + } + // Create the entry data + strncpy (dirEntry.filename, pathEnd, NAME_MAX - 1); + memset (dirEntry.entryData, 0, DIR_ENTRY_DATA_SIZE); + + // Set the creation time and date + dirEntry.entryData[DIR_ENTRY_cTime_ms] = 0; + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_cTime, _FAT_filetime_getTimeFromRTC()); + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_cDate, _FAT_filetime_getDateFromRTC()); + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_mTime, _FAT_filetime_getTimeFromRTC()); + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_mDate, _FAT_filetime_getDateFromRTC()); + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_aDate, _FAT_filetime_getDateFromRTC()); + + // Set the directory attribute + dirEntry.entryData[DIR_ENTRY_attributes] = ATTRIB_DIR; + + // Get a cluster for the new directory + dirCluster = _FAT_fat_linkFreeClusterCleared (partition, CLUSTER_FREE); + if (!_FAT_fat_isValidCluster(partition, dirCluster)) { + // No space left on disc for the cluster + _FAT_unlock(&partition->lock); + r->_errno = ENOSPC; + return -1; + } + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_cluster, dirCluster); + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_clusterHigh, dirCluster >> 16); + + // Write the new directory's entry to it's parent + if (!_FAT_directory_addEntry (partition, &dirEntry, parentCluster)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOSPC; + return -1; + } + + // Create the dot entry within the directory + memset (newEntryData, 0, DIR_ENTRY_DATA_SIZE); + memset (newEntryData, ' ', 11); + newEntryData[DIR_ENTRY_name] = '.'; + newEntryData[DIR_ENTRY_attributes] = ATTRIB_DIR; + u16_to_u8array (newEntryData, DIR_ENTRY_cluster, dirCluster); + u16_to_u8array (newEntryData, DIR_ENTRY_clusterHigh, dirCluster >> 16); + + // Write it to the directory, erasing that sector in the process + _FAT_cache_eraseWritePartialSector ( partition->cache, newEntryData, + _FAT_fat_clusterToSector (partition, dirCluster), 0, DIR_ENTRY_DATA_SIZE); + + + // Create the double dot entry within the directory + + // if ParentDir == Rootdir then ".."" always link to Cluster 0 + if(parentCluster == partition->rootDirCluster) + parentCluster = FAT16_ROOT_DIR_CLUSTER; + + newEntryData[DIR_ENTRY_name + 1] = '.'; + u16_to_u8array (newEntryData, DIR_ENTRY_cluster, parentCluster); + u16_to_u8array (newEntryData, DIR_ENTRY_clusterHigh, parentCluster >> 16); + + // Write it to the directory + _FAT_cache_writePartialSector ( partition->cache, newEntryData, + _FAT_fat_clusterToSector (partition, dirCluster), DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + + // Flush any sectors in the disc cache + if (!_FAT_cache_flush(partition->cache)) { + _FAT_unlock(&partition->lock); + r->_errno = EIO; + return -1; + } + + _FAT_unlock(&partition->lock); + return 0; +} + +int _FAT_rmdir_r (struct _reent *r, const char *path) { + return _FAT_unlinkCommon (r, path, true); +} + +int _FAT_statvfs_r (struct _reent *r, const char *path, struct statvfs *buf) +{ + PARTITION* partition = NULL; + unsigned int freeClusterCount; + + // Get the partition of the requested path + partition = _FAT_partition_getPartitionFromPath (path); + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + _FAT_lock(&partition->lock); + + if(partition->filesysType == FS_FAT32) { + // Sync FSinfo block + _FAT_partition_readFSinfo(partition); + freeClusterCount = partition->fat.numberFreeCluster; + } else { + freeClusterCount = _FAT_fat_freeClusterCount (partition); + } + + // FAT clusters = POSIX blocks + buf->f_bsize = partition->bytesPerCluster; // File system block size. + buf->f_frsize = partition->bytesPerCluster; // Fundamental file system block size. + + buf->f_blocks = partition->fat.lastCluster - CLUSTER_FIRST + 1; // Total number of blocks on file system in units of f_frsize. + buf->f_bfree = freeClusterCount; // Total number of free blocks. + buf->f_bavail = freeClusterCount; // Number of free blocks available to non-privileged process. + + // Treat requests for info on inodes as clusters + buf->f_files = partition->fat.lastCluster - CLUSTER_FIRST + 1; // Total number of file serial numbers. + buf->f_ffree = freeClusterCount; // Total number of free file serial numbers. + buf->f_favail = freeClusterCount; // Number of file serial numbers available to non-privileged process. + + // File system ID. 32bit ioType value + buf->f_fsid = _FAT_disc_hostType(partition->disc); + + // Bit mask of f_flag values. + buf->f_flag = ST_NOSUID /* No support for ST_ISUID and ST_ISGID file mode bits */ + | (partition->readOnly ? ST_RDONLY /* Read only file system */ : 0 ) ; + // Maximum filename length. + buf->f_namemax = NAME_MAX; + + _FAT_unlock(&partition->lock); + return 0; +} + +DIR_ITER* _FAT_diropen_r(struct _reent *r, DIR_ITER *dirState, const char *path) { + DIR_ENTRY dirEntry; + DIR_STATE_STRUCT* state = (DIR_STATE_STRUCT*) (dirState->dirStruct); + bool fileExists; + + state->partition = _FAT_partition_getPartitionFromPath (path); + if (state->partition == NULL) { + r->_errno = ENODEV; + return NULL; + } + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + r->_errno = EINVAL; + return NULL; + } + + _FAT_lock(&state->partition->lock); + + // Get the start cluster of the directory + fileExists = _FAT_directory_entryFromPath (state->partition, &dirEntry, path, NULL); + + if (!fileExists) { + _FAT_unlock(&state->partition->lock); + r->_errno = ENOENT; + return NULL; + } + + // Make sure it is a directory + if (! _FAT_directory_isDirectory (&dirEntry)) { + _FAT_unlock(&state->partition->lock); + r->_errno = ENOTDIR; + return NULL; + } + + // Save the start cluster for use when resetting the directory data + state->startCluster = _FAT_directory_entryGetCluster (state->partition, dirEntry.entryData); + + // Get the first entry for use with a call to dirnext + state->validEntry = + _FAT_directory_getFirstEntry (state->partition, &(state->currentEntry), state->startCluster); + + // We are now using this entry + state->inUse = true; + _FAT_unlock(&state->partition->lock); + return (DIR_ITER*) state; +} + +int _FAT_dirreset_r (struct _reent *r, DIR_ITER *dirState) { + DIR_STATE_STRUCT* state = (DIR_STATE_STRUCT*) (dirState->dirStruct); + + _FAT_lock(&state->partition->lock); + + // Make sure we are still using this entry + if (!state->inUse) { + _FAT_unlock(&state->partition->lock); + r->_errno = EBADF; + return -1; + } + + // Get the first entry for use with a call to dirnext + state->validEntry = + _FAT_directory_getFirstEntry (state->partition, &(state->currentEntry), state->startCluster); + + _FAT_unlock(&state->partition->lock); + return 0; +} + +int _FAT_dirnext_r (struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { + DIR_STATE_STRUCT* state = (DIR_STATE_STRUCT*) (dirState->dirStruct); + + _FAT_lock(&state->partition->lock); + + // Make sure we are still using this entry + if (!state->inUse) { + _FAT_unlock(&state->partition->lock); + r->_errno = EBADF; + return -1; + } + + // Make sure there is another file to report on + if (! state->validEntry) { + _FAT_unlock(&state->partition->lock); + return -1; + } + + // Get the filename + strncpy (filename, state->currentEntry.filename, NAME_MAX); + // Get the stats, if requested + if (filestat != NULL) { + _FAT_directory_entryStat (state->partition, &(state->currentEntry), filestat); + } + + // Look for the next entry for use next time + state->validEntry = + _FAT_directory_getNextEntry (state->partition, &(state->currentEntry)); + + _FAT_unlock(&state->partition->lock); + return 0; +} + +int _FAT_dirclose_r (struct _reent *r, DIR_ITER *dirState) { + DIR_STATE_STRUCT* state = (DIR_STATE_STRUCT*) (dirState->dirStruct); + + // We are no longer using this entry + _FAT_lock(&state->partition->lock); + state->inUse = false; + _FAT_unlock(&state->partition->lock); + + return 0; +} diff --git a/arm9/src/lib/libfat/source/fatdir.h b/arm9/src/lib/libfat/source/fatdir.h new file mode 100644 index 0000000..7f5c71a --- /dev/null +++ b/arm9/src/lib/libfat/source/fatdir.h @@ -0,0 +1,75 @@ +/* + fatdir.h + + Functions used by the newlib disc stubs to interface with + this library + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef _FATDIR_H +#define _FATDIR_H + +#include +#include +#include +#include +#include "common.h" +#include "directory.h" + +typedef struct { + PARTITION* partition; + DIR_ENTRY currentEntry; + uint32_t startCluster; + bool inUse; + bool validEntry; +} DIR_STATE_STRUCT; + +extern int _FAT_stat_r (struct _reent *r, const char *path, struct stat *st); + +extern int _FAT_link_r (struct _reent *r, const char *existing, const char *newLink); + +extern int _FAT_unlink_r (struct _reent *r, const char *name); + +extern int _FAT_chdir_r (struct _reent *r, const char *name); + +extern int _FAT_rename_r (struct _reent *r, const char *oldName, const char *newName); + +extern int _FAT_mkdir_r (struct _reent *r, const char *path, int mode); + +extern int _FAT_rmdir_r (struct _reent *r, const char *path); + +extern int _FAT_statvfs_r (struct _reent *r, const char *path, struct statvfs *buf); + +/* +Directory iterator functions +*/ +extern DIR_ITER* _FAT_diropen_r(struct _reent *r, DIR_ITER *dirState, const char *path); +extern int _FAT_dirreset_r (struct _reent *r, DIR_ITER *dirState); +extern int _FAT_dirnext_r (struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); +extern int _FAT_dirclose_r (struct _reent *r, DIR_ITER *dirState); + + +#endif // _FATDIR_H diff --git a/arm9/src/lib/libfat/source/fatfile.c b/arm9/src/lib/libfat/source/fatfile.c new file mode 100644 index 0000000..cd5ba56 --- /dev/null +++ b/arm9/src/lib/libfat/source/fatfile.c @@ -0,0 +1,1218 @@ +/* + fatfile.c + + Functions used by the newlib disc stubs to interface with + this library + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + 2009-10-23 oggzee: fixes for cluster aligned file size (write, truncate, seek) +*/ + + +#include "fatfile.h" + +#include +#include +#include +#include +#include + +#include "cache.h" +#include "file_allocation_table.h" +#include "bit_ops.h" +#include "filetime.h" +#include "lock.h" + +bool _FAT_findEntry(const char *path, DIR_ENTRY *dirEntry) { + bool r; + PARTITION *partition = _FAT_partition_getPartitionFromPath(path); + + // Check Partition + if( !partition ) + return false; + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + return false; + } + + // Search for the file on the disc + _FAT_lock(&partition->lock); + r = _FAT_directory_entryFromPath (partition, dirEntry, path, NULL); + _FAT_unlock(&partition->lock); + + return r; +} + +int FAT_getAttr(const char *file) { + DIR_ENTRY dirEntry; + if (!_FAT_findEntry(file,&dirEntry)) return -1; + + return dirEntry.entryData[DIR_ENTRY_attributes]; +} + +int FAT_setAttr(const char *file, uint8_t attr) { + + // Defines... + DIR_ENTRY_POSITION entryEnd; + PARTITION *partition = NULL; + DIR_ENTRY dirEntry; + + // Get Partition + partition = _FAT_partition_getPartitionFromPath( file ); + + // Check Partition + if( !partition ) + return -1; + + // Move the path pointer to the start of the actual path + if (strchr (file, ':') != NULL) + file = strchr (file, ':') + 1; + if (strchr (file, ':') != NULL) + return -1; + + // Lock Partition + _FAT_lock(&partition->lock); + + // Get DIR_ENTRY + if( !_FAT_directory_entryFromPath (partition, &dirEntry, file, NULL) ) { + _FAT_unlock(&partition->lock); // Unlock Partition + return -1; + } + + // Get Entry-End + entryEnd = dirEntry.dataEnd; + + // Write Data + _FAT_cache_writePartialSector ( + partition->cache // Cache to write + , &attr // Value to be written + , _FAT_fat_clusterToSector( partition , entryEnd.cluster ) + entryEnd.sector // cluster + , entryEnd.offset * DIR_ENTRY_DATA_SIZE + DIR_ENTRY_attributes // offset + , 1 // Size in bytes + ); + + // Flush any sectors in the disc cache + if ( !_FAT_cache_flush( partition->cache ) ) { + _FAT_unlock(&partition->lock); // Unlock Partition + return -1; + } + + // Unlock Partition + _FAT_unlock(&partition->lock); + + return 0; +} + + +int _FAT_open_r (struct _reent *r, void *fileStruct, const char *path, int flags, int mode) { + PARTITION* partition = NULL; + bool fileExists; + DIR_ENTRY dirEntry; + const char* pathEnd; + uint32_t dirCluster; + FILE_STRUCT* file = (FILE_STRUCT*) fileStruct; + partition = _FAT_partition_getPartitionFromPath (path); + + if (partition == NULL) { + r->_errno = ENODEV; + return -1; + } + + // Move the path pointer to the start of the actual path + if (strchr (path, ':') != NULL) { + path = strchr (path, ':') + 1; + } + if (strchr (path, ':') != NULL) { + r->_errno = EINVAL; + return -1; + } + + // Determine which mode the file is openned for + if ((flags & 0x03) == O_RDONLY) { + // Open the file for read-only access + file->read = true; + file->write = false; + file->append = false; + } else if ((flags & 0x03) == O_WRONLY) { + // Open file for write only access + file->read = false; + file->write = true; + file->append = false; + } else if ((flags & 0x03) == O_RDWR) { + // Open file for read/write access + file->read = true; + file->write = true; + file->append = false; + } else { + r->_errno = EACCES; + return -1; + } + + // Make sure we aren't trying to write to a read-only disc + if (file->write && partition->readOnly) { + r->_errno = EROFS; + return -1; + } + + // Search for the file on the disc + _FAT_lock(&partition->lock); + fileExists = _FAT_directory_entryFromPath (partition, &dirEntry, path, NULL); + + // The file shouldn't exist if we are trying to create it + if ((flags & O_CREAT) && (flags & O_EXCL) && fileExists) { + _FAT_unlock(&partition->lock); + r->_errno = EEXIST; + return -1; + } + + // It should not be a directory if we're openning a file, + if (fileExists && _FAT_directory_isDirectory(&dirEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = EISDIR; + return -1; + } + + // We haven't modified the file yet + file->modified = false; + + // If the file doesn't exist, create it if we're allowed to + if (!fileExists) { + if (flags & O_CREAT) { + if (partition->readOnly) { + // We can't write to a read-only partition + _FAT_unlock(&partition->lock); + r->_errno = EROFS; + return -1; + } + // Create the file + // Get the directory it has to go in + pathEnd = strrchr (path, DIR_SEPARATOR); + if (pathEnd == NULL) { + // No path was specified + dirCluster = partition->cwdCluster; + pathEnd = path; + } else { + // Path was specified -- get the right dirCluster + // Recycling dirEntry, since it needs to be recreated anyway + if (!_FAT_directory_entryFromPath (partition, &dirEntry, path, pathEnd) || + !_FAT_directory_isDirectory(&dirEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOTDIR; + return -1; + } + dirCluster = _FAT_directory_entryGetCluster (partition, dirEntry.entryData); + // Move the pathEnd past the last DIR_SEPARATOR + pathEnd += 1; + } + // Create the entry data + strncpy (dirEntry.filename, pathEnd, NAME_MAX - 1); + memset (dirEntry.entryData, 0, DIR_ENTRY_DATA_SIZE); + + // Set the creation time and date + dirEntry.entryData[DIR_ENTRY_cTime_ms] = 0; + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_cTime, _FAT_filetime_getTimeFromRTC()); + u16_to_u8array (dirEntry.entryData, DIR_ENTRY_cDate, _FAT_filetime_getDateFromRTC()); + + if (!_FAT_directory_addEntry (partition, &dirEntry, dirCluster)) { + _FAT_unlock(&partition->lock); + r->_errno = ENOSPC; + return -1; + } + + // File entry is modified + file->modified = true; + } else { + // file doesn't exist, and we aren't creating it + _FAT_unlock(&partition->lock); + r->_errno = ENOENT; + return -1; + } + } + + file->filesize = u8array_to_u32 (dirEntry.entryData, DIR_ENTRY_fileSize); + + /* Allow LARGEFILEs with undefined results + // Make sure that the file size can fit in the available space + if (!(flags & O_LARGEFILE) && (file->filesize >= (1<<31))) { + r->_errno = EFBIG; + return -1; + } + */ + + // Make sure we aren't trying to write to a read-only file + if (file->write && !_FAT_directory_isWritable(&dirEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = EROFS; + return -1; + } + + // Associate this file with a particular partition + file->partition = partition; + + file->startCluster = _FAT_directory_entryGetCluster (partition, dirEntry.entryData); + + // Truncate the file if requested + if ((flags & O_TRUNC) && file->write && (file->startCluster != 0)) { + _FAT_fat_clearLinks (partition, file->startCluster); + file->startCluster = CLUSTER_FREE; + file->filesize = 0; + // File is modified since we just cut it all off + file->modified = true; + } + + // Remember the position of this file's directory entry + file->dirEntryStart = dirEntry.dataStart; // Points to the start of the LFN entries of a file, or the alias for no LFN + file->dirEntryEnd = dirEntry.dataEnd; + + // Reset read/write pointer + file->currentPosition = 0; + file->rwPosition.cluster = file->startCluster; + file->rwPosition.sector = 0; + file->rwPosition.byte = 0; + + if (flags & O_APPEND) { + file->append = true; + + // Set append pointer to the end of the file + file->appendPosition.cluster = _FAT_fat_lastCluster (partition, file->startCluster); + file->appendPosition.sector = (file->filesize % partition->bytesPerCluster) / partition->bytesPerSector; + file->appendPosition.byte = file->filesize % partition->bytesPerSector; + + // Check if the end of the file is on the end of a cluster + if ( (file->filesize > 0) && ((file->filesize % partition->bytesPerCluster)==0) ){ + // Set flag to allocate a new cluster + file->appendPosition.sector = partition->sectorsPerCluster; + file->appendPosition.byte = 0; + } + } else { + file->append = false; + // Use something sane for the append pointer, so the whole file struct contains known values + file->appendPosition = file->rwPosition; + } + + file->inUse = true; + + // Insert this file into the double-linked list of open files + partition->openFileCount += 1; + if (partition->firstOpenFile) { + file->nextOpenFile = partition->firstOpenFile; + partition->firstOpenFile->prevOpenFile = file; + } else { + file->nextOpenFile = NULL; + } + file->prevOpenFile = NULL; + partition->firstOpenFile = file; + + _FAT_unlock(&partition->lock); + + return (int) file; +} + +/* +Synchronizes the file data to disc. +Does no locking of its own -- lock the partition before calling. +Returns 0 on success, an error code on failure. +*/ +int _FAT_syncToDisc (FILE_STRUCT* file) { + uint8_t dirEntryData[DIR_ENTRY_DATA_SIZE]; + + if (!file || !file->inUse) { + return EBADF; + } + + if (file->write && file->modified) { + // Load the old entry + _FAT_cache_readPartialSector (file->partition->cache, dirEntryData, + _FAT_fat_clusterToSector(file->partition, file->dirEntryEnd.cluster) + file->dirEntryEnd.sector, + file->dirEntryEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + + // Write new data to the directory entry + // File size + u32_to_u8array (dirEntryData, DIR_ENTRY_fileSize, file->filesize); + + // Start cluster + u16_to_u8array (dirEntryData, DIR_ENTRY_cluster, file->startCluster); + u16_to_u8array (dirEntryData, DIR_ENTRY_clusterHigh, file->startCluster >> 16); + + // Modification time and date + u16_to_u8array (dirEntryData, DIR_ENTRY_mTime, _FAT_filetime_getTimeFromRTC()); + u16_to_u8array (dirEntryData, DIR_ENTRY_mDate, _FAT_filetime_getDateFromRTC()); + + // Access date + u16_to_u8array (dirEntryData, DIR_ENTRY_aDate, _FAT_filetime_getDateFromRTC()); + + // Set archive attribute + dirEntryData[DIR_ENTRY_attributes] |= ATTRIB_ARCH; + + // Write the new entry + _FAT_cache_writePartialSector (file->partition->cache, dirEntryData, + _FAT_fat_clusterToSector(file->partition, file->dirEntryEnd.cluster) + file->dirEntryEnd.sector, + file->dirEntryEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE); + + // Flush any sectors in the disc cache + if (!_FAT_cache_flush(file->partition->cache)) { + return EIO; + } + } + + file->modified = false; + + return 0; +} + + +int _FAT_close_r (struct _reent *r, void *fd) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + int ret = 0; + + if (!file->inUse) { + r->_errno = EBADF; + return -1; + } + + _FAT_lock(&file->partition->lock); + + if (file->write) { + ret = _FAT_syncToDisc (file); + if (ret != 0) { + r->_errno = ret; + ret = -1; + } + } + + file->inUse = false; + + // Remove this file from the double-linked list of open files + file->partition->openFileCount -= 1; + if (file->nextOpenFile) { + file->nextOpenFile->prevOpenFile = file->prevOpenFile; + } + if (file->prevOpenFile) { + file->prevOpenFile->nextOpenFile = file->nextOpenFile; + } else { + file->partition->firstOpenFile = file->nextOpenFile; + } + + _FAT_unlock(&file->partition->lock); + + return ret; +} + +ssize_t _FAT_read_r (struct _reent *r, void *fd, char *ptr, size_t len) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + PARTITION* partition; + CACHE* cache; + FILE_POSITION position; + uint32_t tempNextCluster; + unsigned int tempVar; + size_t remain; + bool flagNoError = true; + + // Short circuit cases where len is 0 (or less) + if (len <= 0) { + return 0; + } + + // Make sure we can actually read from the file + if ((file == NULL) || !file->inUse || !file->read) { + r->_errno = EBADF; + return -1; + } + + partition = file->partition; + _FAT_lock(&partition->lock); + + // Don't try to read if the read pointer is past the end of file + if (file->currentPosition >= file->filesize || file->startCluster == CLUSTER_FREE) { + r->_errno = EOVERFLOW; + _FAT_unlock(&partition->lock); + return 0; + } + + // Don't read past end of file + if (len + file->currentPosition > file->filesize) { + r->_errno = EOVERFLOW; + len = file->filesize - file->currentPosition; + } + + remain = len; + position = file->rwPosition; + cache = file->partition->cache; + + // Align to sector + tempVar = partition->bytesPerSector - position.byte; + if (tempVar > remain) { + tempVar = remain; + } + + if ((tempVar < partition->bytesPerSector) && flagNoError) + { + _FAT_cache_readPartialSector ( cache, ptr, _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, + position.byte, tempVar); + + remain -= tempVar; + ptr += tempVar; + + position.byte += tempVar; + if (position.byte >= partition->bytesPerSector) { + position.byte = 0; + position.sector++; + } + } + + // align to cluster + // tempVar is number of sectors to read + if (remain > (partition->sectorsPerCluster - position.sector) * partition->bytesPerSector) { + tempVar = partition->sectorsPerCluster - position.sector; + } else { + tempVar = remain / partition->bytesPerSector; + } + + if ((tempVar > 0) && flagNoError) { + if (! _FAT_cache_readSectors (cache, _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, + tempVar, ptr)) + { + flagNoError = false; + r->_errno = EIO; + } else { + ptr += tempVar * partition->bytesPerSector; + remain -= tempVar * partition->bytesPerSector; + position.sector += tempVar; + } + } + + // Move onto next cluster + // It should get to here without reading anything if a cluster is due to be allocated + if ((position.sector >= partition->sectorsPerCluster) && flagNoError) { + tempNextCluster = _FAT_fat_nextCluster(partition, position.cluster); + if ((remain == 0) && (tempNextCluster == CLUSTER_EOF)) { + position.sector = partition->sectorsPerCluster; + } else if (!_FAT_fat_isValidCluster(partition, tempNextCluster)) { + r->_errno = EIO; + flagNoError = false; + } else { + position.sector = 0; + position.cluster = tempNextCluster; + } + } + + // Read in whole clusters, contiguous blocks at a time + while ((remain >= partition->bytesPerCluster) && flagNoError) { + uint32_t chunkEnd; + uint32_t nextChunkStart = position.cluster; + size_t chunkSize = 0; + + do { + chunkEnd = nextChunkStart; + nextChunkStart = _FAT_fat_nextCluster (partition, chunkEnd); + chunkSize += partition->bytesPerCluster; + } while ((nextChunkStart == chunkEnd + 1) && +#ifdef LIMIT_SECTORS + (chunkSize + partition->bytesPerCluster <= LIMIT_SECTORS * partition->bytesPerSector) && +#endif + (chunkSize + partition->bytesPerCluster <= remain)); + + if (!_FAT_cache_readSectors (cache, _FAT_fat_clusterToSector (partition, position.cluster), + chunkSize / partition->bytesPerSector, ptr)) + { + flagNoError = false; + r->_errno = EIO; + break; + } + ptr += chunkSize; + remain -= chunkSize; + + // Advance to next cluster + if ((remain == 0) && (nextChunkStart == CLUSTER_EOF)) { + position.sector = partition->sectorsPerCluster; + position.cluster = chunkEnd; + } else if (!_FAT_fat_isValidCluster(partition, nextChunkStart)) { + r->_errno = EIO; + flagNoError = false; + } else { + position.sector = 0; + position.cluster = nextChunkStart; + } + } + + // Read remaining sectors + tempVar = remain / partition->bytesPerSector; // Number of sectors left + if ((tempVar > 0) && flagNoError) { + if (!_FAT_cache_readSectors (cache, _FAT_fat_clusterToSector (partition, position.cluster), + tempVar, ptr)) + { + flagNoError = false; + r->_errno = EIO; + } else { + ptr += tempVar * partition->bytesPerSector; + remain -= tempVar * partition->bytesPerSector; + position.sector += tempVar; + } + } + + // Last remaining sector + // Check if anything is left + if ((remain > 0) && flagNoError) { + _FAT_cache_readPartialSector ( cache, ptr, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, 0, remain); + position.byte += remain; + remain = 0; + } + + // Length read is the wanted length minus the stuff not read + len = len - remain; + + // Update file information + file->rwPosition = position; + file->currentPosition += len; + + _FAT_unlock(&partition->lock); + return len; +} + +// if current position is on the cluster border and more data has to be written +// then get next cluster or allocate next cluster +// this solves the over-allocation problems when file size is aligned to cluster size +// return true on succes, false on error +static bool _FAT_check_position_for_next_cluster(struct _reent *r, + FILE_POSITION *position, PARTITION* partition, size_t remain, bool *flagNoError) +{ + uint32_t tempNextCluster; + // do nothing if no more data to write + if (remain == 0) return true; + if (flagNoError && *flagNoError == false) return false; + if (position->sector > partition->sectorsPerCluster) { + // invalid arguments - internal error + r->_errno = EINVAL; + goto err; + } + if (position->sector == partition->sectorsPerCluster) { + // need to advance to next cluster + tempNextCluster = _FAT_fat_nextCluster(partition, position->cluster); + if ((tempNextCluster == CLUSTER_EOF) || (tempNextCluster == CLUSTER_FREE)) { + // Ran out of clusters so get a new one + tempNextCluster = _FAT_fat_linkFreeCluster(partition, position->cluster); + } + if (!_FAT_fat_isValidCluster(partition, tempNextCluster)) { + // Couldn't get a cluster, so abort + r->_errno = ENOSPC; + goto err; + } + position->sector = 0; + position->cluster = tempNextCluster; + } + return true; +err: + if (flagNoError) *flagNoError = false; + return false; +} + +/* +Extend a file so that the size is the same as the rwPosition +*/ +static bool _FAT_file_extend_r (struct _reent *r, FILE_STRUCT* file) { + PARTITION* partition = file->partition; + CACHE* cache = file->partition->cache; + FILE_POSITION position; + uint8_t zeroBuffer [partition->bytesPerSector]; + memset(zeroBuffer, 0, partition->bytesPerSector); + uint32_t remain; + uint32_t tempNextCluster; + unsigned int sector; + + position.byte = file->filesize % partition->bytesPerSector; + position.sector = (file->filesize % partition->bytesPerCluster) / partition->bytesPerSector; + // It is assumed that there is always a startCluster + // This will be true when _FAT_file_extend_r is called from _FAT_write_r + position.cluster = _FAT_fat_lastCluster (partition, file->startCluster); + + remain = file->currentPosition - file->filesize; + + if ((remain > 0) && (file->filesize > 0) && (position.sector == 0) && (position.byte == 0)) { + // Get a new cluster on the edge of a cluster boundary + tempNextCluster = _FAT_fat_linkFreeCluster(partition, position.cluster); + if (!_FAT_fat_isValidCluster(partition, tempNextCluster)) { + // Couldn't get a cluster, so abort + r->_errno = ENOSPC; + return false; + } + position.cluster = tempNextCluster; + position.sector = 0; + } + + if (remain + position.byte < partition->bytesPerSector) { + // Only need to clear to the end of the sector + _FAT_cache_writePartialSector (cache, zeroBuffer, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, position.byte, remain); + position.byte += remain; + } else { + if (position.byte > 0) { + _FAT_cache_writePartialSector (cache, zeroBuffer, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, position.byte, + partition->bytesPerSector - position.byte); + remain -= (partition->bytesPerSector - position.byte); + position.byte = 0; + position.sector ++; + } + + while (remain >= partition->bytesPerSector) { + if (position.sector >= partition->sectorsPerCluster) { + position.sector = 0; + // Ran out of clusters so get a new one + tempNextCluster = _FAT_fat_linkFreeCluster(partition, position.cluster); + if (!_FAT_fat_isValidCluster(partition, tempNextCluster)) { + // Couldn't get a cluster, so abort + r->_errno = ENOSPC; + return false; + } + position.cluster = tempNextCluster; + } + + sector = _FAT_fat_clusterToSector (partition, position.cluster) + position.sector; + _FAT_cache_writeSectors (cache, sector, 1, zeroBuffer); + + remain -= partition->bytesPerSector; + position.sector ++; + } + + if (!_FAT_check_position_for_next_cluster(r, &position, partition, remain, NULL)) { + // error already marked + return false; + } + + if (remain > 0) { + _FAT_cache_writePartialSector (cache, zeroBuffer, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, 0, remain); + position.byte = remain; + } + } + + file->rwPosition = position; + file->filesize = file->currentPosition; + return true; +} + +ssize_t _FAT_write_r (struct _reent *r, void *fd, const char *ptr, size_t len) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + PARTITION* partition; + CACHE* cache; + FILE_POSITION position; + uint32_t tempNextCluster; + unsigned int tempVar; + size_t remain; + bool flagNoError = true; + bool flagAppending = false; + + // Make sure we can actually write to the file + if ((file == NULL) || !file->inUse || !file->write) { + r->_errno = EBADF; + return -1; + } + + partition = file->partition; + cache = file->partition->cache; + _FAT_lock(&partition->lock); + + // Only write up to the maximum file size, taking into account wrap-around of ints + if (len + file->filesize > FILE_MAX_SIZE || len + file->filesize < file->filesize) { + len = FILE_MAX_SIZE - file->filesize; + } + + // Short circuit cases where len is 0 (or less) + if (len <= 0) { + _FAT_unlock(&partition->lock); + return 0; + } + + remain = len; + + // Get a new cluster for the start of the file if required + if (file->startCluster == CLUSTER_FREE) { + tempNextCluster = _FAT_fat_linkFreeCluster (partition, CLUSTER_FREE); + if (!_FAT_fat_isValidCluster(partition, tempNextCluster)) { + // Couldn't get a cluster, so abort immediately + _FAT_unlock(&partition->lock); + r->_errno = ENOSPC; + return -1; + } + file->startCluster = tempNextCluster; + + // Appending starts at the begining for a 0 byte file + file->appendPosition.cluster = file->startCluster; + file->appendPosition.sector = 0; + file->appendPosition.byte = 0; + + file->rwPosition.cluster = file->startCluster; + file->rwPosition.sector = 0; + file->rwPosition.byte = 0; + } + + if (file->append) { + position = file->appendPosition; + flagAppending = true; + } else { + // If the write pointer is past the end of the file, extend the file to that size + if (file->currentPosition > file->filesize) { + if (!_FAT_file_extend_r (r, file)) { + _FAT_unlock(&partition->lock); + return -1; + } + } + + // Write at current read pointer + position = file->rwPosition; + + // If it is writing past the current end of file, set appending flag + if (len + file->currentPosition > file->filesize) { + flagAppending = true; + } + } + + // Move onto next cluster if needed + _FAT_check_position_for_next_cluster(r, &position, partition, remain, &flagNoError); + + // Align to sector + tempVar = partition->bytesPerSector - position.byte; + if (tempVar > remain) { + tempVar = remain; + } + + if ((tempVar < partition->bytesPerSector) && flagNoError) { + // Write partial sector to disk + _FAT_cache_writePartialSector (cache, ptr, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, position.byte, tempVar); + + remain -= tempVar; + ptr += tempVar; + position.byte += tempVar; + + + // Move onto next sector + if (position.byte >= partition->bytesPerSector) { + position.byte = 0; + position.sector ++; + } + } + + // Align to cluster + // tempVar is number of sectors to write + if (remain > (partition->sectorsPerCluster - position.sector) * partition->bytesPerSector) { + tempVar = partition->sectorsPerCluster - position.sector; + } else { + tempVar = remain / partition->bytesPerSector; + } + + if ((tempVar > 0 && tempVar < partition->sectorsPerCluster) && flagNoError) { + if (!_FAT_cache_writeSectors (cache, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, tempVar, ptr)) + { + flagNoError = false; + r->_errno = EIO; + } else { + ptr += tempVar * partition->bytesPerSector; + remain -= tempVar * partition->bytesPerSector; + position.sector += tempVar; + } + } + + // Write whole clusters + while ((remain >= partition->bytesPerCluster) && flagNoError) { + // allocate next cluster + _FAT_check_position_for_next_cluster(r, &position, partition, remain, &flagNoError); + if (!flagNoError) break; + // set indexes to the current position + uint32_t chunkEnd = position.cluster; + uint32_t nextChunkStart = position.cluster; + size_t chunkSize = partition->bytesPerCluster; + FILE_POSITION next_position = position; + + // group consecutive clusters + while (flagNoError && +#ifdef LIMIT_SECTORS + (chunkSize + partition->bytesPerCluster <= LIMIT_SECTORS * partition->bytesPerSector) && +#endif + (chunkSize + partition->bytesPerCluster < remain)) + { + // pretend to use up all sectors in next_position + next_position.sector = partition->sectorsPerCluster; + // get or allocate next cluster + _FAT_check_position_for_next_cluster(r, &next_position, partition, + remain - chunkSize, &flagNoError); + if (!flagNoError) break; // exit loop on error + nextChunkStart = next_position.cluster; + if (nextChunkStart != chunkEnd + 1) break; // exit loop if not consecutive + chunkEnd = nextChunkStart; + chunkSize += partition->bytesPerCluster; + } + + if ( !_FAT_cache_writeSectors (cache, + _FAT_fat_clusterToSector(partition, position.cluster), chunkSize / partition->bytesPerSector, ptr)) + { + flagNoError = false; + r->_errno = EIO; + break; + } + ptr += chunkSize; + remain -= chunkSize; + + if ((chunkEnd != nextChunkStart) && _FAT_fat_isValidCluster(partition, nextChunkStart)) { + // new cluster is already allocated (because it was not consecutive) + position.cluster = nextChunkStart; + position.sector = 0; + } else { + // Allocate a new cluster when next writing the file + position.cluster = chunkEnd; + position.sector = partition->sectorsPerCluster; + } + } + + // allocate next cluster if needed + _FAT_check_position_for_next_cluster(r, &position, partition, remain, &flagNoError); + + // Write remaining sectors + tempVar = remain / partition->bytesPerSector; // Number of sectors left + if ((tempVar > 0) && flagNoError) { + if (!_FAT_cache_writeSectors (cache, _FAT_fat_clusterToSector (partition, position.cluster), tempVar, ptr)) + { + flagNoError = false; + r->_errno = EIO; + } else { + ptr += tempVar * partition->bytesPerSector; + remain -= tempVar * partition->bytesPerSector; + position.sector += tempVar; + } + } + + // Last remaining sector + if ((remain > 0) && flagNoError) { + if (flagAppending) { + _FAT_cache_eraseWritePartialSector ( cache, ptr, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, 0, remain); + } else { + _FAT_cache_writePartialSector ( cache, ptr, + _FAT_fat_clusterToSector (partition, position.cluster) + position.sector, 0, remain); + } + position.byte += remain; + remain = 0; + } + + + // Amount written is the originally requested amount minus stuff remaining + len = len - remain; + + // Update file information + file->modified = true; + if (file->append) { + // Appending doesn't affect the read pointer + file->appendPosition = position; + file->filesize += len; + } else { + // Writing also shifts the read pointer + file->rwPosition = position; + file->currentPosition += len; + if (file->filesize < file->currentPosition) { + file->filesize = file->currentPosition; + } + } + _FAT_unlock(&partition->lock); + + return len; +} + + +off_t _FAT_seek_r (struct _reent *r, void *fd, off_t pos, int dir) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + PARTITION* partition; + uint32_t cluster, nextCluster; + int clusCount; + off_t newPosition; + uint32_t position; + + if ((file == NULL) || (file->inUse == false)) { + // invalid file + r->_errno = EBADF; + return -1; + } + + partition = file->partition; + _FAT_lock(&partition->lock); + + switch (dir) { + case SEEK_SET: + newPosition = pos; + break; + case SEEK_CUR: + newPosition = (off_t)file->currentPosition + pos; + break; + case SEEK_END: + newPosition = (off_t)file->filesize + pos; + break; + default: + _FAT_unlock(&partition->lock); + r->_errno = EINVAL; + return -1; + } + + if ((pos > 0) && (newPosition < 0)) { + _FAT_unlock(&partition->lock); + r->_errno = EOVERFLOW; + return -1; + } + + // newPosition can only be larger than the FILE_MAX_SIZE on platforms where + // off_t is larger than 32 bits. + if (newPosition < 0 || ((sizeof(newPosition) > 4) && newPosition > (off_t)FILE_MAX_SIZE)) { + _FAT_unlock(&partition->lock); + r->_errno = EINVAL; + return -1; + } + + position = (uint32_t)newPosition; + + // Only change the read/write position if it is within the bounds of the current filesize, + // or at the very edge of the file + if (position <= file->filesize && file->startCluster != CLUSTER_FREE) { + // Calculate where the correct cluster is + // how many clusters from start of file + clusCount = position / partition->bytesPerCluster; + cluster = file->startCluster; + if (position >= file->currentPosition) { + // start from current cluster + int currentCount = file->currentPosition / partition->bytesPerCluster; + if (file->rwPosition.sector == partition->sectorsPerCluster) { + currentCount--; + } + clusCount -= currentCount; + cluster = file->rwPosition.cluster; + } + // Calculate the sector and byte of the current position, + // and store them + file->rwPosition.sector = (position % partition->bytesPerCluster) / partition->bytesPerSector; + file->rwPosition.byte = position % partition->bytesPerSector; + + nextCluster = _FAT_fat_nextCluster (partition, cluster); + while ((clusCount > 0) && (nextCluster != CLUSTER_FREE) && (nextCluster != CLUSTER_EOF)) { + clusCount--; + cluster = nextCluster; + nextCluster = _FAT_fat_nextCluster (partition, cluster); + } + + // Check if ran out of clusters and it needs to allocate a new one + if (clusCount > 0) { + if ((clusCount == 1) && (file->filesize == position) && (file->rwPosition.sector == 0)) { + // Set flag to allocate a new cluster + file->rwPosition.sector = partition->sectorsPerCluster; + file->rwPosition.byte = 0; + } else { + _FAT_unlock(&partition->lock); + r->_errno = EINVAL; + return -1; + } + } + + file->rwPosition.cluster = cluster; + } + + // Save position + file->currentPosition = position; + + _FAT_unlock(&partition->lock); + return position; +} + + + +int _FAT_fstat_r (struct _reent *r, void *fd, struct stat *st) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + PARTITION* partition; + DIR_ENTRY fileEntry; + + if ((file == NULL) || (file->inUse == false)) { + // invalid file + r->_errno = EBADF; + return -1; + } + + partition = file->partition; + _FAT_lock(&partition->lock); + + // Get the file's entry data + fileEntry.dataStart = file->dirEntryStart; + fileEntry.dataEnd = file->dirEntryEnd; + + if (!_FAT_directory_entryFromPosition (partition, &fileEntry)) { + _FAT_unlock(&partition->lock); + r->_errno = EIO; + return -1; + } + + // Fill in the stat struct + _FAT_directory_entryStat (partition, &fileEntry, st); + + // Fix stats that have changed since the file was openned + st->st_ino = (ino_t)(file->startCluster); // The file serial number is the start cluster + st->st_size = file->filesize; // File size + + _FAT_unlock(&partition->lock); + return 0; +} + +int _FAT_ftruncate_r (struct _reent *r, void *fd, off_t len) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + PARTITION* partition; + int ret=0; + uint32_t newSize = (uint32_t)len; + + if (len < 0) { + // Trying to truncate to a negative size + r->_errno = EINVAL; + return -1; + } + + if ((sizeof(len) > 4) && len > (off_t)FILE_MAX_SIZE) { + // Trying to extend the file beyond what FAT supports + r->_errno = EFBIG; + return -1; + } + + if (!file || !file->inUse) { + // invalid file + r->_errno = EBADF; + return -1; + } + + if (!file->write) { + // Read-only file + r->_errno = EINVAL; + return -1; + } + + partition = file->partition; + _FAT_lock(&partition->lock); + + if (newSize > file->filesize) { + // Expanding the file + FILE_POSITION savedPosition; + uint32_t savedOffset; + // Get a new cluster for the start of the file if required + if (file->startCluster == CLUSTER_FREE) { + uint32_t tempNextCluster = _FAT_fat_linkFreeCluster (partition, CLUSTER_FREE); + if (!_FAT_fat_isValidCluster(partition, tempNextCluster)) { + // Couldn't get a cluster, so abort immediately + _FAT_unlock(&partition->lock); + r->_errno = ENOSPC; + return -1; + } + file->startCluster = tempNextCluster; + + file->rwPosition.cluster = file->startCluster; + file->rwPosition.sector = 0; + file->rwPosition.byte = 0; + } + // Save the read/write pointer + savedPosition = file->rwPosition; + savedOffset = file->currentPosition; + // Set the position to the new size + file->currentPosition = newSize; + // Extend the file to the new position + if (!_FAT_file_extend_r (r, file)) { + ret = -1; + } + // Set the append position to the new rwPointer + if (file->append) { + file->appendPosition = file->rwPosition; + } + // Restore the old rwPointer; + file->rwPosition = savedPosition; + file->currentPosition = savedOffset; + } else if (newSize < file->filesize){ + // Shrinking the file + if (len == 0) { + // Cutting the file down to nothing, clear all clusters used + _FAT_fat_clearLinks (partition, file->startCluster); + file->startCluster = CLUSTER_FREE; + + file->appendPosition.cluster = CLUSTER_FREE; + file->appendPosition.sector = 0; + file->appendPosition.byte = 0; + } else { + // Trimming the file down to the required size + unsigned int chainLength; + uint32_t lastCluster; + + // Drop the unneeded end of the cluster chain. + // If the end falls on a cluster boundary, drop that cluster too, + // then set a flag to allocate a cluster as needed + chainLength = ((newSize-1) / partition->bytesPerCluster) + 1; + lastCluster = _FAT_fat_trimChain (partition, file->startCluster, chainLength); + + if (file->append) { + file->appendPosition.byte = newSize % partition->bytesPerSector; + // Does the end of the file fall on the edge of a cluster? + if (newSize % partition->bytesPerCluster == 0) { + // Set a flag to allocate a new cluster + file->appendPosition.sector = partition->sectorsPerCluster; + } else { + file->appendPosition.sector = (newSize % partition->bytesPerCluster) / partition->bytesPerSector; + } + file->appendPosition.cluster = lastCluster; + } + } + } else { + // Truncating to same length, so don't do anything + } + + file->filesize = newSize; + file->modified = true; + + _FAT_unlock(&partition->lock); + return ret; +} + +int _FAT_fsync_r (struct _reent *r, void *fd) { + FILE_STRUCT* file = (FILE_STRUCT*) fd; + int ret = 0; + + if (!file->inUse) { + r->_errno = EBADF; + return -1; + } + + _FAT_lock(&file->partition->lock); + + ret = _FAT_syncToDisc (file); + if (ret != 0) { + r->_errno = ret; + ret = -1; + } + + _FAT_unlock(&file->partition->lock); + + return ret; +} diff --git a/arm9/src/lib/libfat/source/fatfile.h b/arm9/src/lib/libfat/source/fatfile.h new file mode 100644 index 0000000..c39aa3f --- /dev/null +++ b/arm9/src/lib/libfat/source/fatfile.h @@ -0,0 +1,103 @@ +/* + fatfile.h + + Functions used by the newlib disc stubs to interface with + this library + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef _FATFILE_H +#define _FATFILE_H + +#include +#include + +#include "common.h" +#include "partition.h" +#include "directory.h" + +#define FILE_MAX_SIZE ((uint32_t)0xFFFFFFFF) // 4GiB - 1B + +typedef struct { + u32 cluster; + sec_t sector; + s32 byte; +} FILE_POSITION; + +struct _FILE_STRUCT; + +struct _FILE_STRUCT { + uint32_t filesize; + uint32_t startCluster; + uint32_t currentPosition; + FILE_POSITION rwPosition; + FILE_POSITION appendPosition; + DIR_ENTRY_POSITION dirEntryStart; // Points to the start of the LFN entries of a file, or the alias for no LFN + DIR_ENTRY_POSITION dirEntryEnd; // Always points to the file's alias entry + PARTITION* partition; + struct _FILE_STRUCT* prevOpenFile; // The previous entry in a double-linked list of open files + struct _FILE_STRUCT* nextOpenFile; // The next entry in a double-linked list of open files + bool read; + bool write; + bool append; + bool inUse; + bool modified; +}; + +typedef struct _FILE_STRUCT FILE_STRUCT; + +int _FAT_open_r (struct _reent *r, void *fileStruct, const char *path, int flags, int mode); + +int _FAT_close_r (struct _reent *r, void *fd); + +ssize_t _FAT_write_r (struct _reent *r,void *fd, const char *ptr, size_t len); + +ssize_t _FAT_read_r (struct _reent *r, void *fd, char *ptr, size_t len); + +off_t _FAT_seek_r (struct _reent *r, void *fd, off_t pos, int dir); + +int _FAT_fstat_r (struct _reent *r, void *fd, struct stat *st); + +int _FAT_stat_r (struct _reent *r, const char *path, struct stat *st); + +int _FAT_link_r (struct _reent *r, const char *existing, const char *newLink); + +int _FAT_chdir_r (struct _reent *r, const char *name); + +int _FAT_rename_r (struct _reent *r, const char *oldName, const char *newName); + +int _FAT_ftruncate_r (struct _reent *r, void *fd, off_t len); + +int _FAT_fsync_r (struct _reent *r, void *fd); + +/* +Synchronizes the file data to disc. +Does no locking of its own -- lock the partition before calling. +Returns 0 on success, an error code on failure. +*/ +extern int _FAT_syncToDisc (FILE_STRUCT* file); + +#endif // _FATFILE_H diff --git a/arm9/src/lib/libfat/source/file_allocation_table.c b/arm9/src/lib/libfat/source/file_allocation_table.c new file mode 100644 index 0000000..72f8aa7 --- /dev/null +++ b/arm9/src/lib/libfat/source/file_allocation_table.c @@ -0,0 +1,393 @@ +/* + file_allocation_table.c + Reading, writing and manipulation of the FAT structure on + a FAT partition + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "file_allocation_table.h" +#include "partition.h" +#include "mem_allocate.h" +#include + +/* +Gets the cluster linked from input cluster +*/ +uint32_t _FAT_fat_nextCluster(PARTITION* partition, uint32_t cluster) +{ + uint32_t nextCluster = CLUSTER_FREE; + sec_t sector; + int offset; + + if (cluster == CLUSTER_FREE) { + return CLUSTER_FREE; + } + + switch (partition->filesysType) + { + case FS_UNKNOWN: + return CLUSTER_ERROR; + break; + + case FS_FAT12: + { + u32 nextCluster_h; + sector = partition->fat.fatStart + (((cluster * 3) / 2) / partition->bytesPerSector); + offset = ((cluster * 3) / 2) % partition->bytesPerSector; + + + _FAT_cache_readLittleEndianValue (partition->cache, &nextCluster, sector, offset, sizeof(u8)); + + offset++; + + if (offset >= partition->bytesPerSector) { + offset = 0; + sector++; + } + nextCluster_h = 0; + + _FAT_cache_readLittleEndianValue (partition->cache, &nextCluster_h, sector, offset, sizeof(u8)); + nextCluster |= (nextCluster_h << 8); + + if (cluster & 0x01) { + nextCluster = nextCluster >> 4; + } else { + nextCluster &= 0x0FFF; + } + + if (nextCluster >= 0x0FF7) + { + nextCluster = CLUSTER_EOF; + } + + break; + } + case FS_FAT16: + sector = partition->fat.fatStart + ((cluster << 1) / partition->bytesPerSector); + offset = (cluster % (partition->bytesPerSector >> 1)) << 1; + + _FAT_cache_readLittleEndianValue (partition->cache, &nextCluster, sector, offset, sizeof(u16)); + + if (nextCluster >= 0xFFF7) { + nextCluster = CLUSTER_EOF; + } + break; + + case FS_FAT32: + sector = partition->fat.fatStart + ((cluster << 2) / partition->bytesPerSector); + offset = (cluster % (partition->bytesPerSector >> 2)) << 2; + + _FAT_cache_readLittleEndianValue (partition->cache, &nextCluster, sector, offset, sizeof(u32)); + + if (nextCluster >= 0x0FFFFFF7) { + nextCluster = CLUSTER_EOF; + } + break; + + default: + return CLUSTER_ERROR; + break; + } + + return nextCluster; +} + +/* +writes value into the correct offset within a partition's FAT, based +on the cluster number. +*/ +static bool _FAT_fat_writeFatEntry (PARTITION* partition, uint32_t cluster, uint32_t value) { + sec_t sector; + int offset; + uint32_t oldValue; + + if ((cluster < CLUSTER_FIRST) || (cluster > partition->fat.lastCluster /* This will catch CLUSTER_ERROR */)) + { + return false; + } + + switch (partition->filesysType) + { + case FS_UNKNOWN: + return false; + break; + + case FS_FAT12: + sector = partition->fat.fatStart + (((cluster * 3) / 2) / partition->bytesPerSector); + offset = ((cluster * 3) / 2) % partition->bytesPerSector; + + if (cluster & 0x01) { + + _FAT_cache_readLittleEndianValue (partition->cache, &oldValue, sector, offset, sizeof(u8)); + + value = (value << 4) | (oldValue & 0x0F); + + _FAT_cache_writeLittleEndianValue (partition->cache, value & 0xFF, sector, offset, sizeof(u8)); + + offset++; + if (offset >= partition->bytesPerSector) { + offset = 0; + sector++; + } + + _FAT_cache_writeLittleEndianValue (partition->cache, (value >> 8) & 0xFF, sector, offset, sizeof(u8)); + + } else { + + _FAT_cache_writeLittleEndianValue (partition->cache, value, sector, offset, sizeof(u8)); + + offset++; + if (offset >= partition->bytesPerSector) { + offset = 0; + sector++; + } + + _FAT_cache_readLittleEndianValue (partition->cache, &oldValue, sector, offset, sizeof(u8)); + + value = ((value >> 8) & 0x0F) | (oldValue & 0xF0); + + _FAT_cache_writeLittleEndianValue (partition->cache, value, sector, offset, sizeof(u8)); + } + + break; + + case FS_FAT16: + sector = partition->fat.fatStart + ((cluster << 1) / partition->bytesPerSector); + offset = (cluster % (partition->bytesPerSector >> 1)) << 1; + + _FAT_cache_writeLittleEndianValue (partition->cache, value, sector, offset, sizeof(u16)); + + break; + + case FS_FAT32: + sector = partition->fat.fatStart + ((cluster << 2) / partition->bytesPerSector); + offset = (cluster % (partition->bytesPerSector >> 2)) << 2; + + _FAT_cache_writeLittleEndianValue (partition->cache, value, sector, offset, sizeof(u32)); + + break; + + default: + return false; + break; + } + + return true; +} + +/*----------------------------------------------------------------- +gets the first available free cluster, sets it +to end of file, links the input cluster to it then returns the +cluster number +If an error occurs, return CLUSTER_ERROR +-----------------------------------------------------------------*/ +uint32_t _FAT_fat_linkFreeCluster(PARTITION* partition, uint32_t cluster) { + uint32_t firstFree; + uint32_t curLink; + uint32_t lastCluster; + bool loopedAroundFAT = false; + + lastCluster = partition->fat.lastCluster; + + if (cluster > lastCluster) { + return CLUSTER_ERROR; + } + + // Check if the cluster already has a link, and return it if so + curLink = _FAT_fat_nextCluster(partition, cluster); + if ((curLink >= CLUSTER_FIRST) && (curLink <= lastCluster)) { + return curLink; // Return the current link - don't allocate a new one + } + + // Get a free cluster + firstFree = partition->fat.firstFree; + // Start at first valid cluster + if (firstFree < CLUSTER_FIRST) { + firstFree = CLUSTER_FIRST; + } + + // Search until a free cluster is found + while (_FAT_fat_nextCluster(partition, firstFree) != CLUSTER_FREE) { + firstFree++; + if (firstFree > lastCluster) { + if (loopedAroundFAT) { + // If couldn't get a free cluster then return an error + partition->fat.firstFree = firstFree; + return CLUSTER_ERROR; + } else { + // Try looping back to the beginning of the FAT + // This was suggested by loopy + firstFree = CLUSTER_FIRST; + loopedAroundFAT = true; + } + } + } + partition->fat.firstFree = firstFree; + if(partition->fat.numberFreeCluster) + partition->fat.numberFreeCluster--; + partition->fat.numberLastAllocCluster = firstFree; + + if ((cluster >= CLUSTER_FIRST) && (cluster <= lastCluster)) + { + // Update the linked from FAT entry + _FAT_fat_writeFatEntry (partition, cluster, firstFree); + } + // Create the linked to FAT entry + _FAT_fat_writeFatEntry (partition, firstFree, CLUSTER_EOF); + + return firstFree; +} + +/*----------------------------------------------------------------- +gets the first available free cluster, sets it +to end of file, links the input cluster to it, clears the new +cluster to 0 valued bytes, then returns the cluster number +If an error occurs, return CLUSTER_ERROR +-----------------------------------------------------------------*/ +uint32_t _FAT_fat_linkFreeClusterCleared (PARTITION* partition, uint32_t cluster) { + uint32_t newCluster; + uint32_t i; + uint8_t *emptySector; + + // Link the cluster + newCluster = _FAT_fat_linkFreeCluster(partition, cluster); + + if (newCluster == CLUSTER_FREE || newCluster == CLUSTER_ERROR) { + return CLUSTER_ERROR; + } + + emptySector = (uint8_t*) _FAT_mem_allocate(partition->bytesPerSector); + + // Clear all the sectors within the cluster + memset (emptySector, 0, partition->bytesPerSector); + for (i = 0; i < partition->sectorsPerCluster; i++) { + _FAT_cache_writeSectors (partition->cache, + _FAT_fat_clusterToSector (partition, newCluster) + i, + 1, emptySector); + } + + _FAT_mem_free(emptySector); + + return newCluster; +} + + +/*----------------------------------------------------------------- +_FAT_fat_clearLinks +frees any cluster used by a file +-----------------------------------------------------------------*/ +bool _FAT_fat_clearLinks (PARTITION* partition, uint32_t cluster) { + uint32_t nextCluster; + + if ((cluster < CLUSTER_FIRST) || (cluster > partition->fat.lastCluster /* This will catch CLUSTER_ERROR */)) + return false; + + // If this clears up more space in the FAT before the current free pointer, move it backwards + if (cluster < partition->fat.firstFree) { + partition->fat.firstFree = cluster; + } + + while ((cluster != CLUSTER_EOF) && (cluster != CLUSTER_FREE) && (cluster != CLUSTER_ERROR)) { + // Store next cluster before erasing the link + nextCluster = _FAT_fat_nextCluster (partition, cluster); + + // Erase the link + _FAT_fat_writeFatEntry (partition, cluster, CLUSTER_FREE); + + if(partition->fat.numberFreeCluster < (partition->numberOfSectors/partition->sectorsPerCluster)) + partition->fat.numberFreeCluster++; + // Move onto next cluster + cluster = nextCluster; + } + + return true; +} + +/*----------------------------------------------------------------- +_FAT_fat_trimChain +Drop all clusters past the chainLength. +If chainLength is 0, all clusters are dropped. +If chainLength is 1, the first cluster is kept and the rest are +dropped, and so on. +Return the last cluster left in the chain. +-----------------------------------------------------------------*/ +uint32_t _FAT_fat_trimChain (PARTITION* partition, uint32_t startCluster, unsigned int chainLength) { + uint32_t nextCluster; + + if (chainLength == 0) { + // Drop the entire chain + _FAT_fat_clearLinks (partition, startCluster); + return CLUSTER_FREE; + } else { + // Find the last cluster in the chain, and the one after it + chainLength--; + nextCluster = _FAT_fat_nextCluster (partition, startCluster); + while ((chainLength > 0) && (nextCluster != CLUSTER_FREE) && (nextCluster != CLUSTER_EOF)) { + chainLength--; + startCluster = nextCluster; + nextCluster = _FAT_fat_nextCluster (partition, startCluster); + } + + // Drop all clusters after the last in the chain + if (nextCluster != CLUSTER_FREE && nextCluster != CLUSTER_EOF) { + _FAT_fat_clearLinks (partition, nextCluster); + } + + // Mark the last cluster in the chain as the end of the file + _FAT_fat_writeFatEntry (partition, startCluster, CLUSTER_EOF); + + return startCluster; + } +} + +/*----------------------------------------------------------------- +_FAT_fat_lastCluster +Trace the cluster links until the last one is found +-----------------------------------------------------------------*/ +uint32_t _FAT_fat_lastCluster (PARTITION* partition, uint32_t cluster) { + while ((_FAT_fat_nextCluster(partition, cluster) != CLUSTER_FREE) && (_FAT_fat_nextCluster(partition, cluster) != CLUSTER_EOF)) { + cluster = _FAT_fat_nextCluster(partition, cluster); + } + return cluster; +} + +/*----------------------------------------------------------------- +_FAT_fat_freeClusterCount +Return the number of free clusters available +-----------------------------------------------------------------*/ +unsigned int _FAT_fat_freeClusterCount (PARTITION* partition) { + unsigned int count = 0; + uint32_t curCluster; + + for (curCluster = CLUSTER_FIRST; curCluster <= partition->fat.lastCluster; curCluster++) { + if (_FAT_fat_nextCluster(partition, curCluster) == CLUSTER_FREE) { + count++; + } + } + + return count; +} + diff --git a/arm9/src/lib/libfat/source/file_allocation_table.h b/arm9/src/lib/libfat/source/file_allocation_table.h new file mode 100644 index 0000000..d9c4361 --- /dev/null +++ b/arm9/src/lib/libfat/source/file_allocation_table.h @@ -0,0 +1,70 @@ +/* + file_allocation_table.h + Reading, writing and manipulation of the FAT structure on + a FAT partition + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _FAT_H +#define _FAT_H + +#include "common.h" +#include "partition.h" + +#define CLUSTER_EOF_16 0xFFFF +#define CLUSTER_EOF 0x0FFFFFFF +#define CLUSTER_FREE 0x00000000 +#define CLUSTER_ROOT 0x00000000 +#define CLUSTER_FIRST 0x00000002 +#define CLUSTER_ERROR 0xFFFFFFFF + +#define CLUSTERS_PER_FAT12 4085 +#define CLUSTERS_PER_FAT16 65525 + + +uint32_t _FAT_fat_nextCluster(PARTITION* partition, uint32_t cluster); + +uint32_t _FAT_fat_linkFreeCluster(PARTITION* partition, uint32_t cluster); +uint32_t _FAT_fat_linkFreeClusterCleared (PARTITION* partition, uint32_t cluster); + +bool _FAT_fat_clearLinks (PARTITION* partition, uint32_t cluster); + +uint32_t _FAT_fat_trimChain (PARTITION* partition, uint32_t startCluster, unsigned int chainLength); + +uint32_t _FAT_fat_lastCluster (PARTITION* partition, uint32_t cluster); + +unsigned int _FAT_fat_freeClusterCount (PARTITION* partition); + +static inline sec_t _FAT_fat_clusterToSector (PARTITION* partition, uint32_t cluster) { + return (cluster >= CLUSTER_FIRST) ? + ((cluster - CLUSTER_FIRST) * (sec_t)partition->sectorsPerCluster) + partition->dataStart : + partition->rootDirStart; +} + +static inline bool _FAT_fat_isValidCluster (PARTITION* partition, uint32_t cluster) { + return (cluster >= CLUSTER_FIRST) && (cluster <= partition->fat.lastCluster /* This will catch CLUSTER_ERROR */); +} + +#endif // _FAT_H diff --git a/arm9/src/lib/libfat/source/filetime.c b/arm9/src/lib/libfat/source/filetime.c new file mode 100644 index 0000000..d297bf6 --- /dev/null +++ b/arm9/src/lib/libfat/source/filetime.c @@ -0,0 +1,107 @@ +/* + filetime.c + Conversion of file time and date values to various other types + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include "filetime.h" +#include "common.h" + +#define MAX_HOUR 23 +#define MAX_MINUTE 59 +#define MAX_SECOND 59 + +#define MAX_MONTH 11 +#define MIN_MONTH 0 +#define MAX_DAY 31 +#define MIN_DAY 1 + +uint16_t _FAT_filetime_getTimeFromRTC (void) { +#ifdef USE_RTC_TIME + struct tm timeParts; + time_t epochTime; + + if (time(&epochTime) == (time_t)-1) { + return 0; + } + localtime_r(&epochTime, &timeParts); + + // Check that the values are all in range. + // If they are not, return 0 (no timestamp) + if ((timeParts.tm_hour < 0) || (timeParts.tm_hour > MAX_HOUR)) return 0; + if ((timeParts.tm_min < 0) || (timeParts.tm_min > MAX_MINUTE)) return 0; + if ((timeParts.tm_sec < 0) || (timeParts.tm_sec > MAX_SECOND)) return 0; + + return ( + ((timeParts.tm_hour & 0x1F) << 11) | + ((timeParts.tm_min & 0x3F) << 5) | + ((timeParts.tm_sec >> 1) & 0x1F) + ); +#else + return 0; +#endif +} + + +uint16_t _FAT_filetime_getDateFromRTC (void) { +#ifdef USE_RTC_TIME + struct tm timeParts; + time_t epochTime; + + if (time(&epochTime) == (time_t)-1) { + return 0; + } + localtime_r(&epochTime, &timeParts); + + if ((timeParts.tm_mon < MIN_MONTH) || (timeParts.tm_mon > MAX_MONTH)) return 0; + if ((timeParts.tm_mday < MIN_DAY) || (timeParts.tm_mday > MAX_DAY)) return 0; + + return ( + (((timeParts.tm_year - 80) & 0x7F) <<9) | // Adjust for MS-FAT base year (1980 vs 1900 for tm_year) + (((timeParts.tm_mon + 1) & 0xF) << 5) | + (timeParts.tm_mday & 0x1F) + ); +#else + return 0; +#endif +} + +time_t _FAT_filetime_to_time_t (uint16_t t, uint16_t d) { + struct tm timeParts; + + timeParts.tm_hour = t >> 11; + timeParts.tm_min = (t >> 5) & 0x3F; + timeParts.tm_sec = (t & 0x1F) << 1; + + timeParts.tm_mday = d & 0x1F; + timeParts.tm_mon = ((d >> 5) & 0x0F) - 1; + timeParts.tm_year = (d >> 9) + 80; + + timeParts.tm_isdst = 0; + + return mktime(&timeParts); +} diff --git a/arm9/src/lib/libfat/source/filetime.h b/arm9/src/lib/libfat/source/filetime.h new file mode 100644 index 0000000..3bfd8ed --- /dev/null +++ b/arm9/src/lib/libfat/source/filetime.h @@ -0,0 +1,41 @@ +/* + filetime.h + Conversion of file time and date values to various other types + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _FILETIME_H +#define _FILETIME_H + +#include "common.h" +#include + +uint16_t _FAT_filetime_getTimeFromRTC (void); +uint16_t _FAT_filetime_getDateFromRTC (void); + +time_t _FAT_filetime_to_time_t (uint16_t t, uint16_t d); + + +#endif // _FILETIME_H diff --git a/arm9/src/lib/libfat/source/libfat.c b/arm9/src/lib/libfat/source/libfat.c new file mode 100644 index 0000000..66a78cf --- /dev/null +++ b/arm9/src/lib/libfat/source/libfat.c @@ -0,0 +1,259 @@ +/* + libfat.c + Simple functionality for startup, mounting and unmounting of FAT-based devices. + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "partition.h" +#include "fatfile.h" +#include "fatdir.h" +#include "lock.h" +#include "mem_allocate.h" +#include "disc.h" + +static const devoptab_t dotab_fat = { + "fat", + sizeof (FILE_STRUCT), + _FAT_open_r, + _FAT_close_r, + _FAT_write_r, + _FAT_read_r, + _FAT_seek_r, + _FAT_fstat_r, + _FAT_stat_r, + _FAT_link_r, + _FAT_unlink_r, + _FAT_chdir_r, + _FAT_rename_r, + _FAT_mkdir_r, + sizeof (DIR_STATE_STRUCT), + _FAT_diropen_r, + _FAT_dirreset_r, + _FAT_dirnext_r, + _FAT_dirclose_r, + _FAT_statvfs_r, + _FAT_ftruncate_r, + _FAT_fsync_r, + NULL, /* Device data */ + NULL, // chmod_r + NULL, // fchmod_r + _FAT_rmdir_r, + _FAT_stat_r, // This is lstat, but we don't support symlinks +}; + +bool fatMount (const char* name, const DISC_INTERFACE* interface, sec_t startSector, uint32_t cacheSize, uint32_t SectorsPerPage, bool isNand) { + PARTITION* partition; + devoptab_t* devops; + char* nameCopy; + + if(!name || strlen(name) > 8 || !interface) + return false; + + /* + if(!isNand) { + if(!interface->startup()) + return false; + }*/ + + if(!interface->isInserted()) + return false; + + char devname[10]; + strcpy(devname, name); + strcat(devname, ":"); + if(FindDevice(devname) >= 0) + return true; + + devops = _FAT_mem_allocate (sizeof(devoptab_t) + strlen(name) + 1); + if (!devops) { + return false; + } + // Use the space allocated at the end of the devoptab struct for storing the name + nameCopy = (char*)(devops+1); + + // Initialize the file system + partition = _FAT_partition_constructor (interface, cacheSize, SectorsPerPage, startSector); + if (!partition) { + _FAT_mem_free (devops); + return false; + } + + // Add an entry for this device to the devoptab table + memcpy (devops, &dotab_fat, sizeof(dotab_fat)); + strcpy (nameCopy, name); + devops->name = nameCopy; + devops->deviceData = partition; + + AddDevice (devops); + + return true; +} + +bool fatMountSimple (const char* name, const DISC_INTERFACE* interface, bool isNand) { + return fatMount (name, interface, 0, DEFAULT_CACHE_PAGES, DEFAULT_SECTORS_PAGE, isNand); +} + +void fatUnmount (const char* name) { + devoptab_t *devops; + PARTITION* partition; + + if(!name) + return; + + devops = (devoptab_t*)GetDeviceOpTab (name); + if (!devops) { + return; + } + + // Perform a quick check to make sure we're dealing with a libfat controlled device + if (devops->open_r != dotab_fat.open_r) { + return; + } + + if (RemoveDevice (name) == -1) { + return; + } + + partition = (PARTITION*)devops->deviceData; + _FAT_partition_destructor (partition); + _FAT_mem_free (devops); +} + +bool fatInit (uint32_t cacheSize, bool setAsDefaultDevice) { + int i; + int defaultDevice = -1; + const DISC_INTERFACE *disc; + + for (i = 0; + _FAT_disc_interfaces[i].name != NULL && _FAT_disc_interfaces[i].getInterface != NULL; + i++) + { + disc = _FAT_disc_interfaces[i].getInterface(); + if (!disc) { + continue; + } + if (fatMount(_FAT_disc_interfaces[i].name, disc, 0, cacheSize, DEFAULT_SECTORS_PAGE, false)) { + // The first device to successfully mount is set as the default + if (defaultDevice < 0) { + defaultDevice = i; + } + } + } + + if (defaultDevice < 0) { + // None of our devices mounted + return false; + } + + if (setAsDefaultDevice) { + char filePath[PATH_MAX]; + strcpy (filePath, _FAT_disc_interfaces[defaultDevice].name); + strcat (filePath, ":/"); +#ifdef ARGV_MAGIC + if ( __system_argv->argvMagic == ARGV_MAGIC && __system_argv->argc >= 1 && strrchr( __system_argv->argv[0], '/' )!=NULL ) { + // Check the app's path against each of our mounted devices, to see + // if we can support it. If so, change to that path. + for (i = 0; + _FAT_disc_interfaces[i].name != NULL && _FAT_disc_interfaces[i].getInterface != NULL; + i++) + { + if ( !strncasecmp( __system_argv->argv[0], _FAT_disc_interfaces[i].name, + strlen(_FAT_disc_interfaces[i].name))) + { + char *lastSlash; + strcpy(filePath, __system_argv->argv[0]); + lastSlash = strrchr( filePath, '/' ); + + if ( NULL != lastSlash) { + if ( *(lastSlash - 1) == ':') lastSlash++; + *lastSlash = 0; + } + } + } + } +#endif + chdir (filePath); + } + + return true; +} + +bool fatInitDefault (void) { + return fatInit (DEFAULT_CACHE_PAGES, true); +} + +void fatGetVolumeLabel (const char* name, char *label) { + devoptab_t *devops; + PARTITION* partition; + char *buf; + int namelen,i; + + if(!name || !label) + return; + + namelen = strlen(name); + buf=(char*)_FAT_mem_allocate(sizeof(char)*namelen+2); + strcpy(buf,name); + + if (name[namelen-1] == '/') { + buf[namelen-1]='\0'; + namelen--; + } + + if (name[namelen-1] != ':') { + buf[namelen]=':'; + buf[namelen+1]='\0'; + } + + devops = (devoptab_t*)GetDeviceOpTab(buf); + + for(i=0;buf[i]!='\0' && buf[i]!=':';i++); + if (!devops || strncasecmp(buf,devops->name,i)) { + _FAT_mem_free(buf); + return; + } + + _FAT_mem_free(buf); + + // Perform a quick check to make sure we're dealing with a libfat controlled device + if (devops->open_r != dotab_fat.open_r) { + return; + } + + partition = (PARTITION*)devops->deviceData; + + if(!_FAT_directory_getVolumeLabel(partition, label)) { + strncpy(label,partition->label,11); + label[11]='\0'; + } + if(!strncmp(label, "NO NAME", 7)) label[0]='\0'; +} diff --git a/arm9/src/lib/libfat/source/lock.c b/arm9/src/lib/libfat/source/lock.c new file mode 100644 index 0000000..59c3444 --- /dev/null +++ b/arm9/src/lib/libfat/source/lock.c @@ -0,0 +1,29 @@ +#include "common.h" + +#ifndef USE_LWP_LOCK + +#ifndef mutex_t +typedef int mutex_t; +#endif + +void __attribute__ ((weak)) _FAT_lock_init(mutex_t *mutex) +{ + return; +} + +void __attribute__ ((weak)) _FAT_lock_deinit(mutex_t *mutex) +{ + return; +} + +void __attribute__ ((weak)) _FAT_lock(mutex_t *mutex) +{ + return; +} + +void __attribute__ ((weak)) _FAT_unlock(mutex_t *mutex) +{ + return; +} + +#endif // USE_LWP_LOCK diff --git a/arm9/src/lib/libfat/source/lock.h b/arm9/src/lib/libfat/source/lock.h new file mode 100644 index 0000000..de5723a --- /dev/null +++ b/arm9/src/lib/libfat/source/lock.h @@ -0,0 +1,72 @@ +/* + lock.h + + Copyright (c) 2008 Sven Peter + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _LOCK_H +#define _LOCK_H + +#include "common.h" + +#ifdef USE_LWP_LOCK + +static inline void _FAT_lock_init(mutex_t *mutex) +{ + LWP_MutexInit(mutex, false); +} + +static inline void _FAT_lock_deinit(mutex_t *mutex) +{ + LWP_MutexDestroy(*mutex); +} + +static inline void _FAT_lock(mutex_t *mutex) +{ + LWP_MutexLock(*mutex); +} + +static inline void _FAT_unlock(mutex_t *mutex) +{ + LWP_MutexUnlock(*mutex); +} + +#else + +// We still need a blank lock type +#ifndef mutex_t +typedef int mutex_t; +#endif + +void _FAT_lock_init(mutex_t *mutex); +void _FAT_lock_deinit(mutex_t *mutex); +void _FAT_lock(mutex_t *mutex); +void _FAT_unlock(mutex_t *mutex); + +#endif // USE_LWP_LOCK + + +#endif // _LOCK_H + diff --git a/arm9/src/lib/libfat/source/mem_allocate.h b/arm9/src/lib/libfat/source/mem_allocate.h new file mode 100644 index 0000000..3308807 --- /dev/null +++ b/arm9/src/lib/libfat/source/mem_allocate.h @@ -0,0 +1,52 @@ +/* + mem_allocate.h + Memory allocation and destruction calls + Replace these calls with custom allocators if + malloc is unavailable + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _MEM_ALLOCATE_H +#define _MEM_ALLOCATE_H + +#include + +static inline void* _FAT_mem_allocate (size_t size) { + return malloc (size); +} + +static inline void* _FAT_mem_align (size_t size) { +#ifdef __wii__ + return memalign (32, size); +#else + return malloc (size); +#endif +} + +static inline void _FAT_mem_free (void* mem) { + free (mem); +} + +#endif // _MEM_ALLOCATE_H diff --git a/arm9/src/lib/libfat/source/partition.c b/arm9/src/lib/libfat/source/partition.c new file mode 100644 index 0000000..4a29261 --- /dev/null +++ b/arm9/src/lib/libfat/source/partition.c @@ -0,0 +1,455 @@ +/* + partition.c + Functions for mounting and dismounting partitions + on various block devices. + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "partition.h" +#include "bit_ops.h" +#include "file_allocation_table.h" +#include "directory.h" +#include "mem_allocate.h" +#include "fatfile.h" + +#include +#include +#include + +/* +Data offsets +*/ + +// BIOS Parameter Block offsets +enum BPB { + BPB_jmpBoot = 0x00, + BPB_OEMName = 0x03, + // BIOS Parameter Block + BPB_bytesPerSector = 0x0B, + BPB_sectorsPerCluster = 0x0D, + BPB_reservedSectors = 0x0E, + BPB_numFATs = 0x10, + BPB_rootEntries = 0x11, + BPB_numSectorsSmall = 0x13, + BPB_mediaDesc = 0x15, + BPB_sectorsPerFAT = 0x16, + BPB_sectorsPerTrk = 0x18, + BPB_numHeads = 0x1A, + BPB_numHiddenSectors = 0x1C, + BPB_numSectors = 0x20, + // Ext BIOS Parameter Block for FAT16 + BPB_FAT16_driveNumber = 0x24, + BPB_FAT16_reserved1 = 0x25, + BPB_FAT16_extBootSig = 0x26, + BPB_FAT16_volumeID = 0x27, + BPB_FAT16_volumeLabel = 0x2B, + BPB_FAT16_fileSysType = 0x36, + // Bootcode + BPB_FAT16_bootCode = 0x3E, + // FAT32 extended block + BPB_FAT32_sectorsPerFAT32 = 0x24, + BPB_FAT32_extFlags = 0x28, + BPB_FAT32_fsVer = 0x2A, + BPB_FAT32_rootClus = 0x2C, + BPB_FAT32_fsInfo = 0x30, + BPB_FAT32_bkBootSec = 0x32, + // Ext BIOS Parameter Block for FAT32 + BPB_FAT32_driveNumber = 0x40, + BPB_FAT32_reserved1 = 0x41, + BPB_FAT32_extBootSig = 0x42, + BPB_FAT32_volumeID = 0x43, + BPB_FAT32_volumeLabel = 0x47, + BPB_FAT32_fileSysType = 0x52, + // Bootcode + BPB_FAT32_bootCode = 0x5A, + BPB_bootSig_55 = 0x1FE, + BPB_bootSig_AA = 0x1FF +}; + +// File system information block offsets +enum FSIB +{ + FSIB_SIG1 = 0x00, + FSIB_SIG2 = 0x1e4, + FSIB_numberOfFreeCluster = 0x1e8, + FSIB_numberLastAllocCluster = 0x1ec, + FSIB_bootSig_55 = 0x1FE, + FSIB_bootSig_AA = 0x1FF +}; + +static const char FAT_SIG[3] = {'F', 'A', 'T'}; +static const char FS_INFO_SIG1[4] = {'R', 'R', 'a', 'A'}; +static const char FS_INFO_SIG2[4] = {'r', 'r', 'A', 'a'}; +static const char FS_TWL_SIG[8] = { 0xe9, 0x00, 0x00, 0x54, 0x57, 0x4c, 0x20, 0x20 }; + +static bool isValidMBR(uint8_t *sectorBuffer) { + return (!memcmp(sectorBuffer + BPB_FAT16_fileSysType, FAT_SIG, sizeof(FAT_SIG)) || + !memcmp(sectorBuffer + BPB_FAT32_fileSysType, FAT_SIG, sizeof(FAT_SIG)) || + !memcmp(sectorBuffer, FS_TWL_SIG, sizeof(FS_TWL_SIG))); + +} + +sec_t FindFirstValidPartition_buf(const DISC_INTERFACE* disc, uint8_t *sectorBuffer) +{ + uint8_t part_table[16*4]; + uint8_t *ptr; + int i; + + // Read first sector of disc + if (!_FAT_disc_readSectors (disc, 0, 1, sectorBuffer)) { + return 0; + } + + memcpy(part_table,sectorBuffer+0x1BE,16*4); + ptr = part_table; + + for(i=0;i<4;i++,ptr+=16) { + sec_t part_lba = u8array_to_u32(ptr, 0x8); + + if (isValidMBR(sectorBuffer)) + { + return part_lba; + } + + if(ptr[4]==0) continue; + + if(ptr[4]==0x0F) { + sec_t part_lba2=part_lba; + sec_t next_lba2=0; + int n; + + for(n=0;n<8;n++) // max 8 logic partitions + { + if(!_FAT_disc_readSectors (disc, part_lba+next_lba2, 1, sectorBuffer)) return 0; + + part_lba2 = part_lba + next_lba2 + u8array_to_u32(sectorBuffer, 0x1C6) ; + next_lba2 = u8array_to_u32(sectorBuffer, 0x1D6); + + if(!_FAT_disc_readSectors (disc, part_lba2, 1, sectorBuffer)) return 0; + + if (isValidMBR(sectorBuffer)) + { + return part_lba2; + } + + if(next_lba2==0) break; + } + } else { + if(!_FAT_disc_readSectors (disc, part_lba, 1, sectorBuffer)) return 0; + + + if (isValidMBR(sectorBuffer)) + { + return part_lba; + } + } + } + return 0; +} + +sec_t FindFirstValidPartition(const DISC_INTERFACE* disc) +{ + uint8_t *sectorBuffer = (uint8_t*) _FAT_mem_align(MAX_SECTOR_SIZE); + if (!sectorBuffer) return 0; + sec_t ret = FindFirstValidPartition_buf(disc, sectorBuffer); + _FAT_mem_free(sectorBuffer); + return ret; +} + + +PARTITION* _FAT_partition_constructor_buf (const DISC_INTERFACE* disc, uint32_t cacheSize, uint32_t sectorsPerPage, sec_t startSector, uint8_t *sectorBuffer) +{ + PARTITION* partition; + + // Read first sector of disc + if (!_FAT_disc_readSectors (disc, startSector, 1, sectorBuffer)) { + return NULL; + } + + // Make sure it is a valid MBR or boot sector + if ( (sectorBuffer[BPB_bootSig_55] != 0x55) || (sectorBuffer[BPB_bootSig_AA] != 0xAA)) { + return NULL; + } + + if (startSector != 0) { + // We're told where to start the partition, so just accept it + } else if (!memcmp(sectorBuffer + BPB_FAT16_fileSysType, FAT_SIG, sizeof(FAT_SIG))) { + // Check if there is a FAT string, which indicates this is a boot sector + startSector = 0; + } else if (!memcmp(sectorBuffer + BPB_FAT32_fileSysType, FAT_SIG, sizeof(FAT_SIG))) { + // Check for FAT32 + startSector = 0; + } else { + startSector = FindFirstValidPartition_buf(disc, sectorBuffer); + if (!_FAT_disc_readSectors (disc, startSector, 1, sectorBuffer)) { + return NULL; + } + } + + if (!isValidMBR(sectorBuffer)) + { + return NULL; + } + + partition = (PARTITION*) _FAT_mem_allocate (sizeof(PARTITION)); + if (partition == NULL) { + return NULL; + } + + // Init the partition lock + _FAT_lock_init(&partition->lock); + + if (!memcmp(sectorBuffer + BPB_FAT16_fileSysType, FAT_SIG, sizeof(FAT_SIG))) + strncpy(partition->label, (char*)(sectorBuffer + BPB_FAT16_volumeLabel), 11); + else + strncpy(partition->label, (char*)(sectorBuffer + BPB_FAT32_volumeLabel), 11); + partition->label[11] = '\0'; + + // Set partition's disc interface + partition->disc = disc; + + // Store required information about the file system + partition->fat.sectorsPerFat = u8array_to_u16(sectorBuffer, BPB_sectorsPerFAT); + if (partition->fat.sectorsPerFat == 0) { + partition->fat.sectorsPerFat = u8array_to_u32( sectorBuffer, BPB_FAT32_sectorsPerFAT32); + } + + partition->numberOfSectors = u8array_to_u16( sectorBuffer, BPB_numSectorsSmall); + if (partition->numberOfSectors == 0) { + partition->numberOfSectors = u8array_to_u32( sectorBuffer, BPB_numSectors); + } + + partition->bytesPerSector = u8array_to_u16(sectorBuffer, BPB_bytesPerSector); + if(partition->bytesPerSector < MIN_SECTOR_SIZE || partition->bytesPerSector > MAX_SECTOR_SIZE) { + // Unsupported sector size + _FAT_mem_free(partition); + return NULL; + } + + partition->sectorsPerCluster = sectorBuffer[BPB_sectorsPerCluster]; + partition->bytesPerCluster = partition->bytesPerSector * partition->sectorsPerCluster; + partition->fat.fatStart = startSector + u8array_to_u16(sectorBuffer, BPB_reservedSectors); + + partition->rootDirStart = partition->fat.fatStart + (sectorBuffer[BPB_numFATs] * partition->fat.sectorsPerFat); + partition->dataStart = partition->rootDirStart + + (( u8array_to_u16(sectorBuffer, BPB_rootEntries) * DIR_ENTRY_DATA_SIZE) / partition->bytesPerSector); + + partition->totalSize = ((uint64_t)partition->numberOfSectors - (partition->dataStart - startSector)) * (uint64_t)partition->bytesPerSector; + + //FS info sector + partition->fsInfoSector = startSector + (u8array_to_u16(sectorBuffer, BPB_FAT32_fsInfo) ? u8array_to_u16(sectorBuffer, BPB_FAT32_fsInfo) : 1); + + // Store info about FAT + uint32_t clusterCount = (partition->numberOfSectors - (uint32_t)(partition->dataStart - startSector)) / partition->sectorsPerCluster; + partition->fat.lastCluster = clusterCount + CLUSTER_FIRST - 1; + partition->fat.firstFree = CLUSTER_FIRST; + partition->fat.numberFreeCluster = 0; + partition->fat.numberLastAllocCluster = 0; + + if (clusterCount < CLUSTERS_PER_FAT12) { + partition->filesysType = FS_FAT12; // FAT12 volume + } else if (clusterCount < CLUSTERS_PER_FAT16) { + partition->filesysType = FS_FAT16; // FAT16 volume + } else { + partition->filesysType = FS_FAT32; // FAT32 volume + } + + if (partition->filesysType != FS_FAT32) { + partition->rootDirCluster = FAT16_ROOT_DIR_CLUSTER; + } else { + // Set up for the FAT32 way + partition->rootDirCluster = u8array_to_u32(sectorBuffer, BPB_FAT32_rootClus); + // Check if FAT mirroring is enabled + if (!(sectorBuffer[BPB_FAT32_extFlags] & 0x80)) { + // Use the active FAT + partition->fat.fatStart = partition->fat.fatStart + ( partition->fat.sectorsPerFat * (sectorBuffer[BPB_FAT32_extFlags] & 0x0F)); + } + } + + // Create a cache to use + partition->cache = _FAT_cache_constructor (cacheSize, sectorsPerPage, partition->disc, startSector+partition->numberOfSectors, partition->bytesPerSector); + + // Set current directory to the root + partition->cwdCluster = partition->rootDirCluster; + + // Check if this disc is writable, and set the readOnly property appropriately + partition->readOnly = !(_FAT_disc_features(disc) & FEATURE_MEDIUM_CANWRITE); + + // There are currently no open files on this partition + partition->openFileCount = 0; + partition->firstOpenFile = NULL; + + _FAT_partition_readFSinfo(partition); + + return partition; +} + +PARTITION* _FAT_partition_constructor (const DISC_INTERFACE* disc, uint32_t cacheSize, uint32_t sectorsPerPage, sec_t startSector) +{ + uint8_t *sectorBuffer = (uint8_t*) _FAT_mem_align(MAX_SECTOR_SIZE); + if (!sectorBuffer) return NULL; + PARTITION *ret = _FAT_partition_constructor_buf(disc, cacheSize, + sectorsPerPage, startSector, sectorBuffer); + _FAT_mem_free(sectorBuffer); + return ret; +} + + +void _FAT_partition_destructor (PARTITION* partition) { + FILE_STRUCT* nextFile; + + _FAT_lock(&partition->lock); + + // Synchronize open files + nextFile = partition->firstOpenFile; + while (nextFile) { + _FAT_syncToDisc (nextFile); + nextFile = nextFile->nextOpenFile; + } + + // Write out the fs info sector + _FAT_partition_writeFSinfo(partition); + + // Free memory used by the cache, writing it to disc at the same time + _FAT_cache_destructor (partition->cache); + + // Unlock the partition and destroy the lock + _FAT_unlock(&partition->lock); + _FAT_lock_deinit(&partition->lock); + + // Free memory used by the partition + _FAT_mem_free (partition); +} + +PARTITION* _FAT_partition_getPartitionFromPath (const char* path) { + const devoptab_t *devops; + + devops = GetDeviceOpTab (path); + + if (!devops) { + return NULL; + } + + return (PARTITION*)devops->deviceData; +} + +static void _FAT_updateFS_INFO(PARTITION * partition, uint8_t *sectorBuffer) { + partition->fat.numberFreeCluster = _FAT_fat_freeClusterCount(partition); + u32_to_u8array(sectorBuffer, FSIB_numberOfFreeCluster, partition->fat.numberFreeCluster); + u32_to_u8array(sectorBuffer, FSIB_numberLastAllocCluster, partition->fat.numberLastAllocCluster); + _FAT_disc_writeSectors (partition->disc, partition->fsInfoSector, 1, sectorBuffer); +} + +void _FAT_partition_createFSinfo(PARTITION * partition) +{ + if(partition->readOnly || partition->filesysType != FS_FAT32) + return; + + uint8_t *sectorBuffer = (uint8_t*) _FAT_mem_align(partition->bytesPerSector); + if (!sectorBuffer) return; + memset(sectorBuffer, 0, partition->bytesPerSector); + + int i; + for(i = 0; i < 4; ++i) + { + sectorBuffer[FSIB_SIG1+i] = FS_INFO_SIG1[i]; + sectorBuffer[FSIB_SIG2+i] = FS_INFO_SIG2[i]; + } + + sectorBuffer[FSIB_bootSig_55] = 0x55; + sectorBuffer[FSIB_bootSig_AA] = 0xAA; + + _FAT_updateFS_INFO(partition,sectorBuffer); + + _FAT_mem_free(sectorBuffer); +} + +void _FAT_partition_readFSinfo(PARTITION * partition) +{ + if(partition->filesysType != FS_FAT32) + return; + + uint8_t *sectorBuffer = (uint8_t*) _FAT_mem_align(partition->bytesPerSector); + if (!sectorBuffer) return; + memset(sectorBuffer, 0, partition->bytesPerSector); + // Read first sector of disc + if (!_FAT_disc_readSectors (partition->disc, partition->fsInfoSector, 1, sectorBuffer)) { + _FAT_mem_free(sectorBuffer); + return; + } + + if(memcmp(sectorBuffer+FSIB_SIG1, FS_INFO_SIG1, 4) != 0 || + memcmp(sectorBuffer+FSIB_SIG2, FS_INFO_SIG2, 4) != 0 || + u8array_to_u32(sectorBuffer, FSIB_numberOfFreeCluster) == 0) + { + //sector does not yet exist, create one! + _FAT_partition_createFSinfo(partition); + } else { + partition->fat.numberFreeCluster = u8array_to_u32(sectorBuffer, FSIB_numberOfFreeCluster); + if(partition->fat.numberFreeCluster == 0xffffffff) { + _FAT_updateFS_INFO(partition,sectorBuffer); + partition->fat.numberFreeCluster = u8array_to_u32(sectorBuffer, FSIB_numberOfFreeCluster); + } + partition->fat.numberLastAllocCluster = u8array_to_u32(sectorBuffer, FSIB_numberLastAllocCluster); + } + _FAT_mem_free(sectorBuffer); +} + +void _FAT_partition_writeFSinfo(PARTITION * partition) +{ + if(partition->filesysType != FS_FAT32) + return; + + uint8_t *sectorBuffer = (uint8_t*) _FAT_mem_align(partition->bytesPerSector); + if (!sectorBuffer) return; + memset(sectorBuffer, 0, partition->bytesPerSector); + // Read first sector of disc + if (!_FAT_disc_readSectors (partition->disc, partition->fsInfoSector, 1, sectorBuffer)) { + _FAT_mem_free(sectorBuffer); + return; + } + + if(memcmp(sectorBuffer+FSIB_SIG1, FS_INFO_SIG1, 4) || memcmp(sectorBuffer+FSIB_SIG2, FS_INFO_SIG2, 4)) { + _FAT_mem_free(sectorBuffer); + return; + } + + u32_to_u8array(sectorBuffer, FSIB_numberOfFreeCluster, partition->fat.numberFreeCluster); + u32_to_u8array(sectorBuffer, FSIB_numberLastAllocCluster, partition->fat.numberLastAllocCluster); + + // Write first sector of disc + _FAT_disc_writeSectors (partition->disc, partition->fsInfoSector, 1, sectorBuffer); + _FAT_mem_free(sectorBuffer); +} + +uint32_t* _FAT_getCwdClusterPtr(const char* name) { + PARTITION *partition = _FAT_partition_getPartitionFromPath(name); + + if (!partition) { + return NULL; + } + + return &partition->cwdCluster; +} diff --git a/arm9/src/lib/libfat/source/partition.h b/arm9/src/lib/libfat/source/partition.h new file mode 100644 index 0000000..ec27a0e --- /dev/null +++ b/arm9/src/lib/libfat/source/partition.h @@ -0,0 +1,107 @@ +/* + partition.h + Functions for mounting and dismounting partitions + on various block devices. + + Copyright (c) 2006 Michael "Chishm" Chisholm + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _PARTITION_H +#define _PARTITION_H + +#include "common.h" +#include "cache.h" +#include "lock.h" + +#define MIN_SECTOR_SIZE 512 +#define MAX_SECTOR_SIZE 4096 + +// Filesystem type +typedef enum {FS_UNKNOWN, FS_FAT12, FS_FAT16, FS_FAT32} FS_TYPE; + +typedef struct { + sec_t fatStart; + uint32_t sectorsPerFat; + uint32_t lastCluster; + uint32_t firstFree; + uint32_t numberFreeCluster; + uint32_t numberLastAllocCluster; +} FAT; + +typedef struct { + const DISC_INTERFACE* disc; + CACHE* cache; + // Info about the partition + FS_TYPE filesysType; + uint64_t totalSize; + sec_t rootDirStart; + uint32_t rootDirCluster; + uint32_t numberOfSectors; + sec_t dataStart; + uint32_t bytesPerSector; + uint32_t sectorsPerCluster; + uint32_t bytesPerCluster; + uint32_t fsInfoSector; + FAT fat; + // Values that may change after construction + uint32_t cwdCluster; // Current working directory cluster + int openFileCount; + struct _FILE_STRUCT* firstOpenFile; // The start of a linked list of files + mutex_t lock; // A lock for partition operations + bool readOnly; // If this is set, then do not try writing to the disc + char label[12]; // Volume label +} PARTITION; + +/* +Mount the supplied device and return a pointer to the struct necessary to use it +*/ +PARTITION* _FAT_partition_constructor (const DISC_INTERFACE* disc, uint32_t cacheSize, uint32_t SectorsPerPage, sec_t startSector); + +/* +Dismount the device and free all structures used. +Will also attempt to synchronise all open files to disc. +*/ +void _FAT_partition_destructor (PARTITION* partition); + +/* +Return the partition specified in a path, as taken from the devoptab. +*/ +PARTITION* _FAT_partition_getPartitionFromPath (const char* path); + +/* +Create the fs info sector. +*/ +void _FAT_partition_createFSinfo(PARTITION * partition); + +/* +Read the fs info sector data. +*/ +void _FAT_partition_readFSinfo(PARTITION * partition); + +/* +Write the fs info sector data. +*/ +void _FAT_partition_writeFSinfo(PARTITION * partition); + +#endif // _PARTITION_H diff --git a/arm9/src/main.c b/arm9/src/main.c index 0abf51f..89c7b8d 100644 --- a/arm9/src/main.c +++ b/arm9/src/main.c @@ -17,52 +17,22 @@ /* TODO: + - LESS NAND WRITES!!!!! - Write good NAND read routine - Detect debugger vs dev - - Create FAT - - Okay so this might be a bad idea in my hands lol - Find an FS lib? No. Fuck no. - - Paste this (encrypted) into sector 0x877 (0x10EE00). - - unsigned char twlMain[56] = { - 0xE9, 0x00, 0x00, 0x54, 0x57, 0x4C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, - 0x02, 0x20, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xF8, 0x34, 0x00, - 0x20, 0x00, 0x10, 0x00, 0x77, 0x08, 0x00, 0x00, 0x89, 0x6F, 0x06, 0x00, - 0x00, 0x00, 0x29, 0x78, 0x56, 0x34, 0x12, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00 - }; - - Pad to 0x200b and make sure to have 0x55AA (CRINGE I HATE MBR I HATE MBR I HATE MBR) <-- I don't even remember writing this. - Now fill 0x200*B88b (total 0x171000) afterwards with zerobytes. - - Congrats. This is a formatted file system. Please don't hurt me. - - Okay but what about twl_photo? Uhhh nobody likes that.... oh fine. - - Paste this (encrypted) into sector 0x6784D (0xCF09A00) - - unsigned char twlPhoto[56] = { - 0xE9, 0x00, 0x00, 0x54, 0x57, 0x4C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, - 0x02, 0x20, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0xF8, 0x09, 0x00, - 0x20, 0x00, 0x10, 0x00, 0x4D, 0x78, 0x06, 0x00, 0xB3, 0x05, 0x01, 0x00, - 0x01, 0x00, 0x29, 0x78, 0x56, 0x34, 0x12, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00 - }; - - Same thing but fill 0x200*13Ab (total 0x27400) afterwards with zerobytes. - - Okay lol how do I do the VBR padding to 0x200 - - Dump VBR start into a variable - - for loop to add the rest of the zero bytes (0x1C8b) - - end with 0x55AA - - Awesome! - - - Fancy windows like TWL EVA + - Recover HWInfo + - Back up/restore screen memory - System transfer (way later on) + + - Why doesn't unmounting NAND get reflected in the file test? + */ +extern bool nand_Startup(); +extern bool sdio_Startup(); +bool nandMounted = false; +bool sdMounted = false; +bool agingMode = false; +bool success = false; bool programEnd = false; bool sdnandMode = true; @@ -80,12 +50,13 @@ PrintConsole topScreen; PrintConsole bottomScreen; typedef enum { - MENUSTATE_FS_MENU, - MENUSTATE_NF_MENU, - MENUSTATE_NULL, - MENUSTATE_TEST, - MENUSTATE_TEST2, - MENUSTATE_EXIT + STARTMENU_FS_MENU, + STARTMENU_NF_MENU, + STARTMENU_NULL, + STARTMENU_TEST, + STARTMENU_TEST2, + STARTMENU_TEST3, + STARTMENU_EXIT } MenuState; static int _mainMenu(int cursor) @@ -102,8 +73,9 @@ static int _mainMenu(int cursor) addMenuItem(m, "FileSystem Menu", NULL, 0, "Options such as repairing MBR\n and formatting twl_main/photo."); addMenuItem(m, "NandFirm menu", NULL, 0, "NandFirm (stage2) installers\n and version testing."); addMenuItem(m, "---------------", NULL, 0, ""); - addMenuItem(m, "Debug1", NULL, 0, "Testing area"); - addMenuItem(m, "Debug2", NULL, 0, "Testing area"); + addMenuItem(m, "Debug1", NULL, 0, "Font display."); + addMenuItem(m, "Debug2", NULL, 0, "MBR corruption test."); + addMenuItem(m, "Debug3", NULL, 0, "NAND AGING test."); addMenuItem(m, "Exit", NULL, 0, "Leave the program."); m->cursor = (cursor); @@ -179,41 +151,29 @@ int main(int argc, char **argv) fifoSetValue32Handler(FIFO_USER_01, fifoHandlerPower, NULL); fifoSetValue32Handler(FIFO_USER_03, fifoHandlerBattery, NULL); - //DSi check + iprintf("\x1B[30m"); + if (!isDSiMode()) { - messageBox("\x1B[31mError:\x1B[33m This app is only for DSi."); + messageBox("\x1B[31mError:\x1B[30m This app is only for DSi."); return 0; } - //setup sd card access - if (!fatInitDefault()) { - messageBox("fatInitDefault()...\x1B[31mFailed\n\x1B[30m\n\nSome features will not work."); - //return 0; + agingMode = true; + + if (!sdio_Startup()) + { + messageBox("\n\x1B[31mERROR: \x1B[30mFailed to mount SD!\n\nSome features will not work."); } - //setup sd card access - if(!nitroFSInit(argv[0])) { - if(!nitroFSInit("TwlNandTool.prod.srl") || !nitroFSGood()) { - if(!nitroFSInit("TwlNandTool.dev.srl") || !nitroFSGood()) { - if(!nitroFSInit("ntrboot.nds") || !nitroFSGood()) { - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } - messageBox("nitroFSInit()...\x1B[31mFailed\n\x1B[30m\nSome features will not work.\n\nTry placing the SRL for your DSion your SD card root like this:\n\nSDMC:/TwlNandTool.prod.srl\nSDMC:/TwlNandTool.dev.srl\nSDMC:/ntrboot.nds\n"); - } - } - } + if (!nand_Startup()) + { + messageBox("\n\x1B[31mFATAL:\x1B[30mStart NAND failed!\nPlease make an issue on GitHub.\n\nThe program will end soon."); } - //setup nand access - if (!fatMountSimple("nand", &io_dsi_nand)) { - messageBox("nand init \x1B[31mfailed\n\x1B[30m\n\nNAND must be repaired."); - } + agingMode = true; + mountMain(); + mountNitroFS(); + agingMode = false; clearScreen(cSUB); clearScreen(cMAIN); @@ -227,26 +187,30 @@ int main(int argc, char **argv) switch (cursor) { - case MENUSTATE_FS_MENU: + case STARTMENU_FS_MENU: fsMain(); break; - case MENUSTATE_NF_MENU: + case STARTMENU_NF_MENU: nfMain(); break; - case MENUSTATE_NULL: + case STARTMENU_NULL: break; - case MENUSTATE_TEST: + case STARTMENU_TEST: debug1(); break; - case MENUSTATE_TEST2: + case STARTMENU_TEST2: debug2(); break; - case MENUSTATE_EXIT: + case STARTMENU_TEST3: + debug3(); + break; + + case STARTMENU_EXIT: programEnd = true; break; } @@ -254,9 +218,14 @@ int main(int argc, char **argv) clearScreen(cSUB); printf("Unmounting NAND...\n"); - fatUnmount("nand:"); - printf("Merging stages...\n"); - nandio_shutdown(); + if(nandMounted) { + fatUnmount("nand"); + } + // I think this was some safety thing but it wants to re-write the entire NAND... + // I'm the one person always saying "NANDs aren't that weak", so you know it's excessive when I comment it out. + + //printf("Merging stages...\n"); + //nandio_shutdown(); fifoSendValue32(FIFO_USER_02, 0x54495845); // 'EXIT' @@ -278,74 +247,76 @@ int debug1(void) { printf("%c ", (char)i); } - iprintf("\n\n Please Push Select To Return "); - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); } int debug2(void) { - clearScreen(cSUB); - iprintf("\n>> Debug1"); - iprintf("\n Stupid AES-CTR BS "); + iprintf("\n>> Corrupt MBR "); + iprintf("\n--------------------------------"); + memset(sector_buf, 0, 0x200); + iprintf("\nWriting new MBR..."); + nand_WriteSectors(0, 1, sector_buf); + iprintf("\nTesting new MBR..."); + nand_ReadSectors(0, 1, sector_buf); + dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); + if(!parse_mbr(sector_buf, is3DS)) { + iprintf("\n\n \x1B[31mERROR!\x1B[30m Failed to break MBR."); + } else { + iprintf("\n\x1B[32mMBR corrupted okay!\x1B[30m"); + } + + exitFunction(); +} + +int debug3(void) { + success = true; + agingMode = true; + clearScreen(cSUB); + + iprintf("\n>> NAND AGING tester "); iprintf("\n--------------------------------"); - nand_ReadSectors(877, 1, sector_buf); - dsi_nand_crypt(sector_buf, sector_buf, 877, SECTOR_SIZE / AES_BLOCK_SIZE); - - printf("\n "); - for (int i = 452; i < SECTOR_SIZE; i++) { - printf("%02X", sector_buf[i]); - if ((i + 1) % 2 == 0) { - printf(" "); - } - if ((i - 443) % 8 == 0 && i != 444) { - printf("\n "); - } + nandPrintInfo(); + if (!nandFirmImport(true)) { + success = false; } + nandFirmRead(); + if (!repairMbr(true)) { + success = false; + } + readMbr(); - iprintf("\n\n Read in"); + if (success == true && !mountMain()) { + if (!formatMain() && !formatPhoto() && !mountMain()) { + iprintf("\nNAND mount failed!"); + success = false; + } else { + nandMounted = true; + } + } else if (nandMounted == true) { + iprintf("\nNAND already mounted."); + } else { + nandMounted = true; + } + filetestMain(); + unmountMain(); + filetestNitro(); - while (true) - { - swiWaitForVBlank(); - scanKeys(); + agingMode = false; - if (keysDown() & KEY_SELECT ) - break; + iprintf("\n>> NAND AGING tester result "); + iprintf("\n--------------------------------"); + + if (success == true) { + iprintf("\nAll tests passed okay."); + } else { + iprintf("\nTests failed!"); } - dsi_nand_crypt(sector_buf, sector_buf, 877, SECTOR_SIZE / AES_BLOCK_SIZE); - - printf("\n "); - for (int i = 444; i < SECTOR_SIZE; i++) { - printf("%02X", sector_buf[i]); - if ((i + 1) % 2 == 0) { - printf(" "); - } - if ((i - 443) % 8 == 0 && i != 444) { - printf("\n "); - } - } - - iprintf("\n\n Read out"); - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); + return 1; } void clearScreen(enum console c) diff --git a/arm9/src/main.h b/arm9/src/main.h index 05e3958..6a46b20 100644 --- a/arm9/src/main.h +++ b/arm9/src/main.h @@ -10,6 +10,11 @@ #include "video.h" #include "nitrofs.h" +extern bool nandMounted; +extern bool sdMounted; +extern bool agingMode; +extern bool success; + extern bool programEnd; extern bool sdnandMode; extern bool unlaunchFound; @@ -27,6 +32,7 @@ void backupMenu(); void testMenu(); int debug1(); int debug2(); +int debug3(); extern PrintConsole topScreen; extern PrintConsole bottomScreen; diff --git a/arm9/src/menu.c b/arm9/src/menu.c index ad8df74..7b21b28 100644 --- a/arm9/src/menu.c +++ b/arm9/src/menu.c @@ -339,10 +339,33 @@ bool moveCursor(Menu* m) return !(lastCursor == m->cursor); } + +void wait(int ticks){ + while(ticks--)swiWaitForVBlank(); +} + char downloadPlayLoading(int number) { char pictoload[] = {(char)142, (char)143, (char)144, (char)145, (char)146, (char)147, (char)148, (char)149}; static int counter = 0; counter = (counter % 7) + 1; return pictoload[counter]; +} + +void exitFunction() { + if (agingMode == false) { + iprintf("\n\n Please Push Select To Return "); + while (true) + { + swiWaitForVBlank(); + scanKeys(); + + if (keysDown() & KEY_SELECT ) + break; + } + } else { + wait(50); + } + clearScreen(cSUB); + // Restore screen memory here } \ No newline at end of file diff --git a/arm9/src/menu.h b/arm9/src/menu.h index cd6f2ac..91ff40c 100644 --- a/arm9/src/menu.h +++ b/arm9/src/menu.h @@ -40,5 +40,5 @@ void printMenu(Menu* m, int level); bool moveCursor(Menu* m); char downloadPlayLoading(int number); - +void exitFunction(); #endif \ No newline at end of file diff --git a/arm9/src/nand/filesystem.c b/arm9/src/nand/filesystem.c index 5b7788a..2d0e64e 100644 --- a/arm9/src/nand/filesystem.c +++ b/arm9/src/nand/filesystem.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "f_xy.h" #include "twltool/dsi.h" #include "nandio.h" @@ -15,6 +16,9 @@ #include "../main.h" #include "../video.h" #include "../menu.h" +#include "../lib/libfat/include/fat.h" + +extern bool nand_Startup(); // Master Boot Records u8 mbrSamsung[68] = { @@ -50,54 +54,21 @@ u8 vbrPhoto[54] = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }; -// File tables for /sys/ folder and HWInfo secure -// Why? HWInfo is always at one of 3 set offsets. I use those offsets for HWInfo recovery. -// We will copy over these file tables to ensure the offsets stay the same. - -// No idea what this is but I need it lol -u8 fileTable1[16] = { - 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0x00, 0x00 -}; - -// File table for /sys/ folder -u8 fileTable2[64] = { - 0x41, 0x73, 0x00, 0x79, 0x00, 0x73, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x0F, - 0x00, 0x54, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x59, 0x53, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, - 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x28, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00 -}; - -// File table for HWInfo -u8 fileTable3[192] = { - 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x75, 0x02, - 0x64, 0x59, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x2E, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, - 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x28, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xE5, 0x6C, 0x00, 0x6F, 0x00, 0x67, 0x00, 0x00, - 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xE5, 0x4F, 0x47, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x00, 0x00, 0x01, 0x00, 0x21, 0x28, 0x64, 0x59, 0x00, 0x00, 0x01, 0x00, - 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x48, 0x00, 0x57, - 0x00, 0x49, 0x00, 0x4E, 0x00, 0x46, 0x00, 0x0F, 0x00, 0x9C, 0x4F, 0x00, - 0x5F, 0x00, 0x53, 0x00, 0x2E, 0x00, 0x64, 0x00, 0x61, 0x00, 0x00, 0x00, - 0x74, 0x00, 0x00, 0x00, 0x48, 0x57, 0x49, 0x4E, 0x46, 0x4F, 0x5F, 0x53, - 0x44, 0x41, 0x54, 0x20, 0x00, 0x00, 0x01, 0x00, 0x21, 0x28, 0x64, 0x59, - 0x00, 0x00, 0x50, 0x02, 0x64, 0x59, 0x06, 0x00, 0x00, 0x40, 0x00, 0x00 -}; - static size_t i; enum { - READ_MBR, - REPAIR_MBR, - FORMAT_MAIN, - FORMAT_PHOTO, - BACK + FSMENU_READ_MBR, + FSMENU_REPAIR_MBR, + FSMENU_FORMAT_MAIN, + FSMENU_FORMAT_PHOTO, + FSMENU_NULL, + FSMENU_MOUNT_MAIN, + FSMENU_UNMOUNT_MAIN, + FSMENU_MOUNT_NITRO, + FSMENU_NULL2, + FSMENU_FILETEST_MAIN, + FSMENU_FILETEST_NITRO, + FSMENU_BACK }; static int _fsMenu(int cursor) @@ -112,6 +83,13 @@ static int _fsMenu(int cursor) addMenuItem(m, "Repair MBR", NULL, 0, "Repair the Master Boot Record."); addMenuItem(m, "Format TWL_MAIN", NULL, 0, "Format the partition where the\n firmware, apps, and saves are\n installed.\n\n THIS WILL ERASE EVERYTHING."); addMenuItem(m, "Format TWL_PHOTO", NULL, 0, "Format the partition where\n photos are stored.\n\n THIS WILL ERASE EVERYTHING."); + addMenuItem(m, "------------------", NULL, 0, ""); + addMenuItem(m, "Mount TWL_MAIN", NULL, 0, "Mount the TWL_MAIN partition."); + addMenuItem(m, "Unmount TWL_MAIN", NULL, 0, "Unmount the TWL_MAIN partition"); + addMenuItem(m, "Mount NitroFS", NULL, 0, "Mount the ROM filesystem."); + addMenuItem(m, "------------------", NULL, 0, ""); + addMenuItem(m, "File test TWL_MAIN", NULL, 0, "Attempt to make/delete dummy\n file on NAND."); + addMenuItem(m, "File test NitroFS", NULL, 0, "Attempt to read file in\n NitroFS."); addMenuItem(m, "Back", NULL, 0, "Leave the FileSystem menu."); m->cursor = cursor; @@ -149,23 +127,49 @@ int fsMain(void) switch (cursor) { - case READ_MBR: + case FSMENU_READ_MBR: readMbr(); break; - case REPAIR_MBR: - repairMbr(); + case FSMENU_REPAIR_MBR: + repairMbr(false); break; - case FORMAT_MAIN: + case FSMENU_FORMAT_MAIN: formatMain(); break; - case FORMAT_PHOTO: + case FSMENU_FORMAT_PHOTO: formatPhoto(); break; - case BACK: + case FSMENU_NULL: + break; + + case FSMENU_MOUNT_MAIN: + mountMain(); + break; + + case FSMENU_UNMOUNT_MAIN: + unmountMain(); + break; + + case FSMENU_MOUNT_NITRO: + mountNitroFS(); + break; + + case FSMENU_NULL2: + break; + + case FSMENU_FILETEST_MAIN: + filetestMain(); + break; + + case FSMENU_FILETEST_NITRO: + filetestNitro(); + break; + + case FSMENU_BACK: programEnd = true; break; } @@ -176,15 +180,20 @@ int fsMain(void) return 0; } -int readMbr(void) { +bool readMbr(void) { + success = true; clearScreen(cSUB); - nand_ReadSectors(0, 1, sector_buf); - dsi_crypt_init((const u8*)consoleIDfixed, (const u8*)0x2FFD7BC, is3DS); - dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); - iprintf("\n>> MBR (Master Boot Record) "); - iprintf("\n--------------------------------"); + iprintf("\n--------------------------------"); + + if(nand_ReadSectors(0, 1, sector_buf) == false) { + iprintf("\nCouldn't read NAND!\n"); + success = false; + } + + dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); + printf("\n "); for (i = 444; i < SECTOR_SIZE; i++) { printf("%02X", sector_buf[i]); @@ -197,124 +206,103 @@ int readMbr(void) { } if(parse_mbr(sector_buf, is3DS)) { iprintf("\n\n \x1B[31mERROR!\x1B[30m MBR is corrupted."); + success = false; } - iprintf("\n\n Please Push Select To Return "); - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); + return success; } -int repairMbr(void) { +bool repairMbr(bool autofix) { + success = true; clearScreen(cSUB); iprintf("\n>> Repair Corrupted MBR "); iprintf("\n--------------------------------"); - if(!parse_mbr(sector_buf, is3DS)) { - if (choicePrint("NAND does not look corrupt.\nRepair anyway?") == NO) { - return 0; + if (parse_mbr(sector_buf, is3DS) || nandMounted == false) { + if(!parse_mbr(sector_buf, is3DS) && autofix == false) { + if (choicePrint("NAND does not look corrupt.\nRepair anyway?") == NO) { + exitFunction(); + return success; + } } - } - iprintf("\nNAND type : %s (0x%02X)", nandInfo.NAND_PNM, nandInfo.NAND_MID); + iprintf("\nNAND type : %s (0x%02X)", nandInfo.NAND_PNM, nandInfo.NAND_MID); - memset(sector_buf, 0, 444); - if (nandInfo.NAND_MID == 0x15) { - memcpy(sector_buf + 444, mbrSamsung, sizeof(mbrSamsung)); - } else if (nandInfo.NAND_MID == 0xFE) { - memcpy(sector_buf + 444, mbrSt, sizeof(mbrSt)); - } + memset(sector_buf, 0, 444); + if (nandInfo.NAND_MID == 0x15) { + memcpy(sector_buf + 444, mbrSamsung, sizeof(mbrSamsung)); + } else if (nandInfo.NAND_MID == 0xFE) { + memcpy(sector_buf + 444, mbrSt, sizeof(mbrSt)); + } - // Write new MBR, encrypt it, then save it. - // Afterwards we read it back from NAND and decrypt to confirm it works. - // - // No need to do safety checks before writing since corrupted MBR is already broken. - iprintf("\nWriting new MBR..."); + // Write new MBR, encrypt it, then save it. + // Afterwards we read it back from NAND and decrypt to confirm it works. + // + // No need to do safety checks before writing since corrupted MBR is already broken. + iprintf("\nWriting new MBR..."); - dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); - nand_WriteSectors(0, 1, sector_buf); + dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); + nand_WriteSectors(0, 1, sector_buf); - iprintf("\nTesting new MBR..."); + iprintf("\nTesting new MBR..."); - nand_ReadSectors(0, 1, sector_buf); - dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); + nand_ReadSectors(0, 1, sector_buf); + dsi_nand_crypt(sector_buf, sector_buf, 0, SECTOR_SIZE / AES_BLOCK_SIZE); - if(parse_mbr(sector_buf, is3DS)) { - iprintf("\n\n \x1B[31mERROR!\x1B[30m Failed to fix MBR."); + if(parse_mbr(sector_buf, is3DS)) { + iprintf("\n\n \x1B[31mERROR!\x1B[30m Failed to fix MBR."); + success = false; + } else { + iprintf("\n\x1B[32mThe new MBR passed!\x1B[30m"); + } } else { - iprintf("\n\x1B[32mThe new MBR passed!\x1B[30m"); + iprintf("\nNAND is currently mounted!\n\nSkipping MBR repair since\nthe FS had no issues."); } - iprintf("\n\n Please Push Select To Return "); - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); + return success; } -int formatMain(void) { +bool formatMain(void) { + success = true; clearScreen(cSUB); iprintf("\n>> Format TWL_MAIN "); iprintf("\n--------------------------------"); - iprintf("\n\nWriting VBR..."); - memset(file_buf, 0, 0x200); - // Write the first 54 bytes, then pad to 0x1FE. Finally write 0x55AA - memcpy(file_buf, vbrMain, sizeof(vbrMain)); - memset(file_buf + sizeof(vbrMain), 0, (BUFFER_SIZE - sizeof(vbrMain) - 2)); - memset(file_buf + SECTOR_SIZE - 2, 0x55, 1); - memset(file_buf + SECTOR_SIZE - 1, 0xAA, 1); - good_nandio_write(0x10EE00, 0x200, file_buf, true); - iprintf("\nClearing file tables..."); - memset(file_buf, 0, 0x600); - for (i = 0; i < 0x3d8; i++) { - good_nandio_write(0x10EE00 + 0x200 + (0x600 * i), 0x600, file_buf, true); + if (nandMounted == false) { + iprintf("\nWriting VBR..."); + memset(file_buf, 0, 0x200); + // Write the first 54 bytes, then pad to 0x1FE. Finally write 0x55AA + memcpy(file_buf, vbrMain, sizeof(vbrMain)); + memset(file_buf + sizeof(vbrMain), 0, (BUFFER_SIZE - sizeof(vbrMain) - 2)); + memset(file_buf + SECTOR_SIZE - 2, 0x55, 1); + memset(file_buf + SECTOR_SIZE - 1, 0xAA, 1); + good_nandio_write(0x10EE00, 0x200, file_buf, true); + iprintf("\nClearing file tables..."); + memset(file_buf, 0, 0x600); + for (i = 0; i < 0x3d8; i++) { + good_nandio_write(0x10EE00 + 0x200 + (0x600 * i), 0x600, file_buf, true); + } + iprintf("\nAll done!"); + } else { + iprintf("\nNAND is currently mounted!\n\nSkipping TWL_MAIN format since\nthe FS had no issues."); } - iprintf("\nMaking new file tables..."); - memset(file_buf, 0, 0x200); - memcpy(file_buf, fileTable1, sizeof(fileTable1)); - good_nandio_write(0x115800, sizeof(fileTable1), file_buf, true); - memset(file_buf, 0, 0x200); - memcpy(file_buf, fileTable2, sizeof(fileTable2)); - good_nandio_write(0x11C000, sizeof(fileTable2), file_buf, true); - memset(file_buf, 0, 0x200); - memcpy(file_buf, fileTable3, sizeof(fileTable3)); - good_nandio_write(0x120000, sizeof(fileTable3), file_buf, true); - iprintf("\nAll done!"); - - iprintf("\n\n Please Push Select To Return "); - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); + return success; } -int formatPhoto(void) { +bool formatPhoto(void) { + success = true; clearScreen(cSUB); iprintf("\n>> Format TWL_PHOTO "); iprintf("\n--------------------------------"); - iprintf("\n\nWriting VBR..."); + iprintf("\nWriting VBR..."); memset(file_buf, 0, 0x200); // Write the first 54 bytes, then pad to 0x1FE. Finally write 0x55AA memcpy(file_buf, vbrPhoto, sizeof(vbrPhoto)); @@ -330,14 +318,208 @@ int formatPhoto(void) { iprintf("\nAll done!"); - iprintf("\n\n Please Push Select To Return "); + exitFunction(); + return success; +} - while (true) - { - swiWaitForVBlank(); - scanKeys(); +bool mountMain(void) { + success = true; + clearScreen(cSUB); - if (keysDown() & KEY_SELECT ) - break; - } + iprintf("\n>> Mount TWL_MAIN "); + iprintf("\n--------------------------------"); + + if (nandMounted == true) { + if (!agingMode) { + agingMode = true; + unmountMain(); + agingMode = false; + } else { + unmountMain(); + } + } + + clearScreen(cSUB); + iprintf("\n>> Mount TWL_MAIN "); + iprintf("\n--------------------------------"); + + if (!nandio_startup()) { + iprintf("\nFailed startup!"); + success = false; + } + if (success == true && !fatMountSimple("nand", &io_dsi_nand, true)) { + iprintf("\n\x1B[31mMount TWL_MAIN failed!\n\x1B[30m\nNAND must be repaired. Try...\n - Fixing MBR\n - Formatting TWL_MAIN"); + success = false; + } else if (success == true) { + iprintf("\nTWL_MAIN mounted okay."); + nandio_unlock_writing(); + nandMounted = true; + } + + exitFunction(); + return success; +} + +bool unmountMain(void) { + success = true; + clearScreen(cSUB); + + iprintf("\n>> Unmount TWL_MAIN "); + iprintf("\n--------------------------------"); + + if (nandMounted == true) { + iprintf("\nUnmounting NAND..."); + fatUnmount("nand"); + nandMounted = false; + sdMounted = false; + + // I just don't want to interrupt the startup aging test + if (!agingMode) { + agingMode = true; + success = mountNitroFS(); + agingMode = false; + } else { + success = mountNitroFS(); + } + + clearScreen(cSUB); + iprintf("\n>> Umount TWL_MAIN "); + iprintf("\n--------------------------------"); + + if (success) { + iprintf("\nTWL_MAIN unmounted okay."); + } else { + iprintf("\nFailed to mount TWL_MAIN!"); + } + + } else { + iprintf("\nNAND not mounted!\nDoing nothing."); + } + + exitFunction(); + return success; +} + +bool mountNitroFS(void) { + success = true; + clearScreen(cSUB); + + iprintf("\n>> Mount NitroFS "); + iprintf("\n--------------------------------"); + + if (sdMounted == false && !fatInitDefault()) { + iprintf("\nSD card not mounted!"); + success = false; + } else { + iprintf("\nSD card mounted okay."); + sdMounted = true; + } + + if(!nitroFSInit("TwlNandTool.prod.srl") || !nitroFSGood()) { + if(!nitroFSInit("TwlNandTool.dev.srl") || !nitroFSGood()) { + if(!nitroFSInit("ntrboot.nds") || !nitroFSGood()) { + iprintf("\x1B[31mFailed to mount NitroFS!\n\x1B[30m\nSome features will not work.\n\nTry placing the SRL for your DSiat one of these locations:\n\nSDMC:/TwlNandTool.prod.srl\nSDMC:/TwlNandTool.dev.srl\nSDMC:/ntrboot.nds\n"); + success = false; + } + } + } + + if (success) { + iprintf("\nNitroFS mounted okay."); + } + + exitFunction(); + return success; +} + +bool filetestMain(void) { + success = true; + clearScreen(cSUB); + + iprintf("\n>> Read TWL_MAIN file "); + iprintf("\n--------------------------------"); + + if (nandMounted == true) { + printf("\nMaking temp folder..."); + mkdir("nand:/tmp", 0777); + + printf("\nOpening test file...\n"); + + char file_path[100]; + snprintf(file_path, 100, "nand:/tmp/test.bin"); + FILE *file = fopen(file_path, "wb"); + printf("\n%s\n", file_path); + if(file) { + iprintf("\nWrite test..."); + memset(sector_buf, 0, SECTOR_SIZE); + memcpy(sector_buf + 444, mbrSamsung, sizeof(mbrSamsung)); + fwrite(sector_buf, 1, SECTOR_SIZE, file); + fclose(file); + iprintf("\nFile closed."); + } else { + success = false; + iprintf("\nFile failed to open!"); + } + + memset(sector_buf, 0, SECTOR_SIZE); + + iprintf("\nOpening file..."); + FILE *file2 = fopen(file_path, "rb"); + if(file2) { + iprintf("\nRead test..."); + fread(sector_buf, 1, SECTOR_SIZE, file2); + if(parse_mbr(sector_buf, is3DS)) { + iprintf("\n\n \x1B[31mERROR!\x1B[30m File did not save!"); + success = false; + } else { + iprintf("\n\x1B[32mThe file is okay!\x1B[30m"); + } + fclose(file2); + iprintf("\nFile closed."); + } else { + success = false; + iprintf("\nFile failed to open!"); + } + } else { + success = false; + iprintf("\nTWL_MAIN is not mounted!"); + } + + exitFunction(); + return success; +} + +bool filetestNitro(void) { + success = true; + clearScreen(cSUB); + + iprintf("\n>> Read NitroFS file "); + iprintf("\n--------------------------------"); + + printf("\nOpening test file...\n"); + + char file_path[100]; + snprintf(file_path, 100, "nitro:/version_twlnandtool"); + FILE *file = fopen(file_path, "r"); + printf("\n%s\n", file_path); + if(file) { + fseek(file, 0, SEEK_END); + int length = ftell(file); + fseek(file, 0, SEEK_SET); + + char *version = (char *)malloc((length) * sizeof(char)); + version[length] = '\0'; + + fread(version, 1, length, file); + + printf("\n%s\n", version); + fclose(file); + + iprintf("\nFile opened okay."); + } else { + success = false; + iprintf("\nFile failed to open!"); + } + exitFunction(); + return success; } \ No newline at end of file diff --git a/arm9/src/nand/filesystem.h b/arm9/src/nand/filesystem.h index 2bff151..dee3289 100644 --- a/arm9/src/nand/filesystem.h +++ b/arm9/src/nand/filesystem.h @@ -16,7 +16,13 @@ #include "../menu.h" int fsMain(void); -int readMbr(void); -int repairMbr(void); -int formatMain(void); -int formatPhoto(void); \ No newline at end of file +bool readMbr(void); +bool repairMbr(bool autofix); +bool formatMain(void); +bool formatPhoto(void); +bool mountMain(void); +bool unmountMain(void); +bool mountNitroFS(void); + +bool filetestMain(void); +bool filetestNitro(void); \ No newline at end of file diff --git a/arm9/src/nand/nandfirm.c b/arm9/src/nand/nandfirm.c index 0e5c7f7..ade512c 100644 --- a/arm9/src/nand/nandfirm.c +++ b/arm9/src/nand/nandfirm.c @@ -14,8 +14,6 @@ #include "../main.h" #include "../video.h" -bool success = true; - void death(char *message, u8 *buffer){ iprintf("\n%s\n", message); free(buffer); @@ -108,8 +106,8 @@ int nfMain(void) return 0; } -int nandFirmRead(void) { - +bool nandFirmRead(void) { + success = true; clearScreen(cSUB); iprintf("\n>> NandFirm Version Checker "); @@ -117,11 +115,7 @@ int nandFirmRead(void) { int fail=0; - // Sectors are 0x200 each, this is sector 626 OR offset 0x4E400 - // nand_ReadSectors(, , ) - if(nand_ReadSectors(626, 1, sector_buf) == false){ - iprintf("Nand read error"); - } + nand_ReadSectors(626, 1, sector_buf); printf("\n Ver: "); for (i = 0; i < 10; i++) { @@ -136,28 +130,18 @@ int nandFirmRead(void) { } printf("\n"); - iprintf("\n\n\n Please Push Select To Return "); - - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); return success; } bool nandFirmImport(bool sdmc) { - + success = true; clearScreen(cSUB); iprintf("\n>> Import NandFirm (%s) ", sdmc ? "sdmc" : "menu"); iprintf("\n--------------------------------"); - printf("Opening NandFirm...\n"); + printf("\nOpening NandFirm...\n"); char file_path[100]; snprintf(file_path, 100, "nitro:/import/%s/%s-launcher.nand", consoleSignName, sdmc ? "sdmc" : "menu"); @@ -179,21 +163,12 @@ bool nandFirmImport(bool sdmc) { iprintf("\nNandFirm import failed!"); } - iprintf("\n\n Please Push Select To Return "); - - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } - return success; + exitFunction(); + return success; } -int nandPrintInfo(void) { - +bool nandPrintInfo(void) { + success = true; extern nandData nandInfo; clearScreen(cSUB); iprintf("\n>> CID (Card IDentification)"); @@ -216,15 +191,7 @@ int nandPrintInfo(void) { printf("\n "); } } - iprintf("\n\n\n Please Push Select To Return "); - while (true) - { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_SELECT ) - break; - } + exitFunction(); return success; } \ No newline at end of file diff --git a/arm9/src/nand/nandfirm.h b/arm9/src/nand/nandfirm.h index e0503c3..db288fa 100644 --- a/arm9/src/nand/nandfirm.h +++ b/arm9/src/nand/nandfirm.h @@ -16,9 +16,8 @@ void wait(int ticks); void death(char *message, u8 *buffer); - int nfMain(void); -int nandFirmRead(void); +bool nandFirmRead(void); bool nandFirmImport(bool sdmc); -int nandPrintInfo(void); \ No newline at end of file +bool nandPrintInfo(void); \ No newline at end of file diff --git a/arm9/src/nand/nandio.c b/arm9/src/nand/nandio.c index 0eb2033..fe26914 100644 --- a/arm9/src/nand/nandio.c +++ b/arm9/src/nand/nandio.c @@ -119,10 +119,6 @@ void nandGetInfo(void) { bool nandio_startup() { - if (!nand_Startup()) - { - return false; - } nand_ReadSectors(0, 1, sector_buf); is3DS = parse_ncsd(sector_buf) == 0; @@ -250,6 +246,7 @@ bool nandio_write_sectors(sec_t offset, sec_t len, const void *buffer) } bool good_nandio_write(int inputAddress, int inputLength, u8 *buffer, bool crypt) { + nandWritten = true; // Sorry lol I just don't want to deal with sector calculation int byteOffset = inputAddress % SECTOR_SIZE; int sectorNum = inputAddress / SECTOR_SIZE; @@ -310,6 +307,7 @@ bool good_nandio_write(int inputAddress, int inputLength, u8 *buffer, bool crypt } bool good_nandio_write_file(int inputAddress, int inputLength, FILE *fp, bool crypt) { + nandWritten = true; int byteOffset = inputAddress % SECTOR_SIZE; int sectorNum = inputAddress / SECTOR_SIZE; int byteEndOffset = (inputAddress+inputLength) % SECTOR_SIZE; @@ -381,11 +379,11 @@ bool nandio_shutdown() u8 stagingLevels = sector_buf[0x10]; u8 reservedSectors = sector_buf[0x0E]; u16 sectorsPerFatCopy = sector_buf[0x16] | ((u16)sector_buf[0x17] << 8); - /* + /* iprintf("[i] Staging for %i FAT copies\n",stagingLevels); iprintf("[i] Stages starting at %i\n",reservedSectors); iprintf("[i] %i sectors per stage\n",sectorsPerFatCopy); - */ + */ if (stagingLevels > 1) { for (u32 sector = 0;sector < sectorsPerFatCopy; sector++) diff --git a/arm9/src/nitrofs.c b/arm9/src/nitrofs.c index 2e6d19f..88e625d 100644 --- a/arm9/src/nitrofs.c +++ b/arm9/src/nitrofs.c @@ -450,7 +450,7 @@ bool nitroFSGood(void) { fread(version, 1, length, file); - printf("VER |%s|%s|\n", version, VERSION); + //printf("VER |%s|%s|\n", version, VERSION); nitroFSGood = (strcmp(version, VERSION) == 0); fclose(file); } diff --git a/nitrofiles/import/common/HWINFO_N.dat b/nitrofiles/import/common/HWINFO_N.dat new file mode 100755 index 0000000..3116144 Binary files /dev/null and b/nitrofiles/import/common/HWINFO_N.dat differ diff --git a/nitrofiles/import/dev/TWLFontTable.dat b/nitrofiles/import/common/TWLFontTable.dat similarity index 100% rename from nitrofiles/import/dev/TWLFontTable.dat rename to nitrofiles/import/common/TWLFontTable.dat diff --git a/nitrofiles/import/dev/TWLFontTable_CN.dat b/nitrofiles/import/common/TWLFontTable_CN.dat similarity index 100% rename from nitrofiles/import/dev/TWLFontTable_CN.dat rename to nitrofiles/import/common/TWLFontTable_CN.dat diff --git a/nitrofiles/import/dev/TWLFontTable_KR.dat b/nitrofiles/import/common/TWLFontTable_KR.dat similarity index 100% rename from nitrofiles/import/dev/TWLFontTable_KR.dat rename to nitrofiles/import/common/TWLFontTable_KR.dat diff --git a/nitrofiles/import/prod/TWLFontTable.dat b/nitrofiles/import/prod/TWLFontTable.dat deleted file mode 100644 index 9d42be0..0000000 Binary files a/nitrofiles/import/prod/TWLFontTable.dat and /dev/null differ diff --git a/nitrofiles/import/prod/TWLFontTable_CN.dat b/nitrofiles/import/prod/TWLFontTable_CN.dat deleted file mode 100644 index 3fdbc81..0000000 Binary files a/nitrofiles/import/prod/TWLFontTable_CN.dat and /dev/null differ diff --git a/nitrofiles/import/prod/TWLFontTable_KR.dat b/nitrofiles/import/prod/TWLFontTable_KR.dat deleted file mode 100755 index a2393bb..0000000 Binary files a/nitrofiles/import/prod/TWLFontTable_KR.dat and /dev/null differ