55
66/* A directory entry looks like:
77 *
8- * 00-09: ten byte filename FFFFFFFF.EE
9- * 0a-0b: word: start sector
10- * 0c-17: unknown
8+ * 00-07: name, space-padded ASCII (high bit may be set as attribute flag → mask with 0x7f)
9+ * 08-09: 2-char type code (SY, SM, ST, SR, LS, FH, BS, IM, KS …)
10+ * 0a-0b: uint16 LE start_sector – first data sector (0-based absolute)
11+ * 0c-0d: uint16 LE end_sector – first sector after the file (exclusive)
12+ * 0e-0f: uint16 LE flags – purpose unknown
13+ * 10-11: uint16 LE last_bytes – bytes used in last sector;
14+ * 0 means last sector is completely full
15+ * exact_size = (size_sectors-1)*256 + last_bytes if last_bytes > 0
16+ * size_sectors * 256 if last_bytes == 0
17+ * 12-13: load address – high byte then low byte (reliable for type SM)
18+ * 14-15: entry point – same encoding
19+ * 16: month BCD (0x07 = July); 0x00 or 0xFF = no date
20+ * 17: year BCD (0x82 = 1982); 0x00 or 0xFF = no date
1121 */
1222
1323class Smaky6Filesystem : public Filesystem
@@ -57,37 +67,62 @@ class Smaky6Filesystem : public Filesystem
5767 filename = ss.str ();
5868 }
5969
60- std::string metadataBytes;
61- {
62- std::stringstream ss;
63-
64- for (int i = 10 ; i < 0x18 ; i++)
65- ss << fmt::format (" {:02x} " , (uint8_t )dbuf[i]);
66-
67- metadataBytes = ss.str ();
68- }
69-
7070 ByteReader br (dbuf);
71-
72- br.skip (10 ); /* filename */
71+ br.skip (10 ); /* skip filename/type already parsed above */
7372 startSector = br.read_le16 ();
74- endSector = br.read_le16 ();
75- br.skip ( 2 ); /* unknown */
73+ endSector = br.read_le16 ();
74+ uint16_t flags = br.read_le16 ( ); /* purpose unknown */
7675 lastSectorLength = br.read_le16 ();
77-
78- file_type = TYPE_FILE ;
79- length = (endSector - startSector - 1 ) * 256 + lastSectorLength;
76+ uint8_t loadHi = br.read_8 ();
77+ uint8_t loadLo = br.read_8 ();
78+ uint8_t entryHi = br.read_8 ();
79+ uint8_t entryLo = br.read_8 ();
80+ uint8_t monthBcd = br.read_8 ();
81+ uint8_t yearBcd = br.read_8 ();
82+
83+ /* Decode BCD date; 0x00 and 0xFF both mean "no date" */
84+ auto bcdToInt = [](uint8_t b) -> int {
85+ return (b >> 4 ) * 10 + (b & 0x0f );
86+ };
87+ int month = (monthBcd && monthBcd != 0xff ) ? bcdToInt (monthBcd) : 0 ;
88+ int year = (yearBcd && yearBcd != 0xff ) ? bcdToInt (yearBcd) : 0 ;
89+
90+ uint16_t loadAddr = ((uint16_t )loadHi << 8 ) | loadLo;
91+ uint16_t entryAddr = ((uint16_t )entryHi << 8 ) | entryLo;
92+
93+ /* DR entries are sub-directory containers, not plain files. */
94+ file_type = (filename.size () > 3 &&
95+ filename.substr (filename.size () - 3 ) == " .DR" )
96+ ? TYPE_DIRECTORY : TYPE_FILE ;
97+ /* When lastSectorLength == 0 the last sector is completely full;
98+ * FluxEngine's original formula subtracted 256 bytes in that case. */
99+ length = lastSectorLength
100+ ? (endSector - startSector - 1 ) * 256 + lastSectorLength
101+ : (endSector - startSector) * 256 ;
80102
81103 path = {filename};
82104 attributes[Filesystem::FILENAME ] = filename;
83- attributes[Filesystem::LENGTH ] = std::to_string (length);
105+ attributes[Filesystem::LENGTH ] = std::to_string (length);
84106 attributes[Filesystem::FILE_TYPE ] = " file" ;
85- attributes[Filesystem::MODE ] = " " ;
107+ attributes[Filesystem::MODE ] = " " ;
86108 attributes[" smaky6.start_sector" ] = std::to_string (startSector);
87- attributes[" smaky6.end_sector" ] = std::to_string (endSector);
88- attributes[" smaky6.sectors" ] =
89- std::to_string (endSector - startSector);
90- attributes[" smaky6.metadata_bytes" ] = metadataBytes;
109+ attributes[" smaky6.end_sector" ] = std::to_string (endSector);
110+ attributes[" smaky6.sectors" ] = std::to_string (endSector - startSector);
111+ attributes[" smaky6.flags" ] = fmt::format (" 0x{:04x}" , flags);
112+ if (loadAddr)
113+ attributes[" smaky6.load_addr" ] = fmt::format (" 0x{:04x}" , loadAddr);
114+ if (entryAddr)
115+ attributes[" smaky6.entry_addr" ] = fmt::format (" 0x{:04x}" , entryAddr);
116+ if (month && year)
117+ {
118+ static const char * months[] = {" " ," Jan" ," Feb" ," Mar" ," Apr" ," May" ," Jun" ,
119+ " Jul" ," Aug" ," Sep" ," Oct" ," Nov" ," Dec" };
120+ int yearFull = (year >= 78 ) ? 1900 + year : 2000 + year;
121+ attributes[" smaky6.date" ] =
122+ fmt::format (" {} {}" ,
123+ (month >= 1 && month <= 12 ) ? months[month] : " ?" ,
124+ yearFull);
125+ }
91126 }
92127
93128 public:
@@ -100,33 +135,42 @@ class Smaky6Filesystem : public Filesystem
100135 class Directory
101136 {
102137 public:
103- Directory (Smaky6Filesystem* fs)
138+ /* drStartSector=0 reads the root directory; any other value reads
139+ * the sub-directory stored in the first 3 sectors of a DR entry. */
140+ Directory (Smaky6Filesystem* fs, unsigned drStartSector = 0 )
141+ {
142+ auto bytes = fs->getLogicalSector (drStartSector, 3 );
143+ parseFrom (bytes, drStartSector);
144+ }
145+
146+ std::shared_ptr<SmakyDirent> findFile (const std::string& filename)
104147 {
105- /* Read the directory. */
148+ for (auto & de : dirents)
149+ if (de->filename == filename)
150+ return de;
106151
107- auto bytes = fs-> getLogicalSector ( 0 , 3 );
108- ByteReader br (bytes);
152+ throw FileNotFoundException ( );
153+ }
109154
155+ private:
156+ void parseFrom (const Bytes& bytes, unsigned sectorBase)
157+ {
110158 for (int i = 0 ; i < 32 ; i++)
111159 {
112160 auto dbuf = bytes.slice (i * 0x18 , 0x18 );
113- if (dbuf[0 ])
161+ /* 0x00 = empty slot; 0xFF = deleted entry */
162+ if (dbuf[0 ] && dbuf[0 ] != 0xff )
114163 {
115164 auto de = std::make_shared<SmakyDirent>(dbuf);
165+ /* Sub-directory entries use relative sector numbers;
166+ * add the DR container's base sector to get absolute ones. */
167+ de->startSector += sectorBase;
168+ de->endSector += sectorBase;
116169 dirents.push_back (de);
117170 }
118171 }
119172 }
120173
121- std::shared_ptr<SmakyDirent> findFile (const std::string& filename)
122- {
123- for (auto & de : dirents)
124- if (de->filename == filename)
125- return de;
126-
127- throw FileNotFoundException ();
128- }
129-
130174 public:
131175 std::vector<std::shared_ptr<SmakyDirent>> dirents;
132176 };
@@ -167,40 +211,55 @@ class Smaky6Filesystem : public Filesystem
167211
168212 std::vector<std::shared_ptr<Dirent>> list (const Path& path) override
169213 {
170- if (!path.empty ())
171- throw FileNotFoundException ();
172-
173- Directory dir (this );
174214 std::vector<std::shared_ptr<Dirent>> result;
175- for (auto & de : dir .dirents )
215+ for (auto & de : directoryAt (path) .dirents )
176216 result.push_back (de);
177217 return result;
178218 }
179219
180220 std::shared_ptr<Dirent> getDirent (const Path& path) override
181221 {
182- Directory dir (this );
183- if (path.size () != 1 )
184- throw BadPathException ();
185-
186- return dir.findFile (path[0 ]);
222+ return resolveDirent (path);
187223 }
188224
189225 Bytes getFile (const Path& path) override
190226 {
191- if (path.size () != 1 )
227+ auto de = resolveDirent (path);
228+ if (de->file_type == TYPE_DIRECTORY )
192229 throw BadPathException (path);
230+ Bytes data = getLogicalSector (
231+ de->startSector , de->endSector - de->startSector );
232+ return data.slice (0 , de->length );
233+ }
193234
194- Directory dir (this );
195- auto de = dir.findFile (path[0 ]);
235+ private:
236+ /* Returns the Directory whose entries are named by path.
237+ * path=[] → root; path=["DIR.DR"] → DR sub-directory.
238+ * The Smaky 6 FS is at most two levels deep; deeper paths are invalid. */
239+ Directory directoryAt (const Path& path)
240+ {
241+ if (path.empty ())
242+ return Directory (this );
243+ if (path.size () == 1 )
244+ {
245+ auto parent = Directory (this ).findFile (path[0 ]);
246+ if (parent->file_type != TYPE_DIRECTORY )
247+ throw BadPathException (path);
248+ return Directory (this , parent->startSector );
249+ }
250+ throw BadPathException (path);
251+ }
196252
197- Bytes data =
198- getLogicalSector (de->startSector , de->endSector - de->startSector );
199- data = data.slice (0 , de->length );
200- return data;
253+ /* Resolves a full path to its SmakyDirent.
254+ * path=["FILE"] → file in root; path=["DIR.DR","FILE"] → file in sub-dir. */
255+ std::shared_ptr<SmakyDirent> resolveDirent (const Path& path)
256+ {
257+ if (path.empty ())
258+ throw BadPathException (path);
259+ Path parentPath (path.begin (), path.end () - 1 );
260+ return directoryAt (parentPath).findFile (path.back ());
201261 }
202262
203- private:
204263 const Smaky6FsProto& _config;
205264};
206265
0 commit comments