// license:BSD-3-Clause// copyright-holders:Olivier Galibert// Management of VTech images#include"fs_vtech.h"#include"fsblk.h"#include"vt_dsk.h"#include"multibyte.h"#include<stdexcept>usingnamespacefs;namespacefs{constvtech_imageVTECH;}// Floppy only, format is 40 tracks, 1 head, 16 sectors numbered 0-15, 256 bytes/sector.// Filesystem has no subdirectories.//// Track 0 sectors 0-14 have the file names. 16 bytes/entry// offset 0 : File type 'T' (basic), 'B' (binary), or some other letter (application-specific)// offset 1 : 0x3a// offset 2-9: File name// offset a : Track number of first file sector// offset b : Sector number of first file sector// offset c-d: Start address (little-endian)// offset e-f: End address (little-endian)//// Files are stored with 126 bytes/sector, and bytes 126 and 127 are// track/sector of the next file data sector.namespace{classvtech_impl:publicfilesystem_t{public:vtech_impl(fsblk_t&blockdev);virtual~vtech_impl()=default;virtualmeta_datavolume_metadata()override;virtualstd::error_conditionvolume_metadata_change(constmeta_data&info)override;virtualstd::pair<std::error_condition,meta_data>metadata(conststd::vector<std::string>&path)override;virtualstd::error_conditionmetadata_change(conststd::vector<std::string>&path,constmeta_data&meta)override;virtualstd::pair<std::error_condition,std::vector<dir_entry>>directory_contents(conststd::vector<std::string>&path)override;virtualstd::error_conditionrename(conststd::vector<std::string>&opath,conststd::vector<std::string>&npath)override;virtualstd::error_conditionremove(conststd::vector<std::string>&path)override;virtualstd::error_conditionfile_create(conststd::vector<std::string>&path,constmeta_data&meta)override;virtualstd::pair<std::error_condition,std::vector<u8>>file_read(conststd::vector<std::string>&path)override;virtualstd::error_conditionfile_write(conststd::vector<std::string>&path,conststd::vector<u8>&data)override;virtualstd::error_conditionformat(constmeta_data&meta)override;private:meta_datafile_metadata(constu8*entry);std::tuple<fsblk_t::block_t,u32>file_find(std::string_viewname);std::vector<std::pair<u8,u8>>allocate_blocks(u32count);voidfree_blocks(conststd::vector<std::pair<u8,u8>>&blocks);u32free_block_count();};}constchar*vtech_image::name()const{return"vtech";}constchar*vtech_image::description()const{return"VTech (Laser 200/300)";}voidvtech_image::enumerate_f(floppy_enumerator&fe)const{fe.add(FLOPPY_VTECH_BIN_FORMAT,floppy_image::FF_525,floppy_image::SSSD,163840,"vtech","VTech");}std::unique_ptr<filesystem_t>vtech_image::mount(fsblk_t&blockdev)const{returnstd::make_unique<vtech_impl>(blockdev);}boolvtech_image::can_format()const{returntrue;}boolvtech_image::can_read()const{returntrue;}boolvtech_image::can_write()const{returntrue;}boolvtech_image::has_rsrc()const{returnfalse;}std::vector<meta_description>vtech_image::volume_meta_description()const{std::vector<meta_description>res;returnres;}std::vector<meta_description>vtech_image::file_meta_description()const{std::vector<meta_description>res;res.emplace_back(meta_description(meta_name::name,"",false,[](constmeta_value&m){returnm.as_string().size()<=8;},"File name, 8 chars"));res.emplace_back(meta_description(meta_name::loading_address,0x7ae9,false,[](constmeta_value&m){returnm.as_number()<0x10000;},"Loading address of the file"));res.emplace_back(meta_description(meta_name::length,0,true,nullptr,"Size of the file in bytes"));res.emplace_back(meta_description(meta_name::file_type,"T",true,[](constmeta_value&m){returnm.as_string().size()==1&&m.as_string()[0]>='A'&&m.as_string()[0]<='Z';},"File type (e.g. T = text, B = binary)"));returnres;}vtech_impl::vtech_impl(fsblk_t&blockdev):filesystem_t(blockdev,128){}std::error_conditionvtech_impl::format(constmeta_data&meta){m_blockdev.fill(0);returnstd::error_condition();}meta_datavtech_impl::volume_metadata(){returnmeta_data();}std::error_conditionvtech_impl::volume_metadata_change(constmeta_data&meta){returnstd::error_condition();}meta_datavtech_impl::file_metadata(constu8*entry){meta_datares;res.set(meta_name::name,trim_end_spaces(rstr(entry+2,8)));res.set(meta_name::file_type,std::string{char(entry[0])});res.set(meta_name::loading_address,get_u16le(entry+0xc));res.set(meta_name::length,(get_u16le(entry+0xe)-get_u16le(entry+0xc))&0xffff);returnres;}std::tuple<fsblk_t::block_t,u32>vtech_impl::file_find(std::string_viewname){for(intsect=0;sect!=14;sect++){autobdir=m_blockdev.get(sect);for(u32i=0;i!=8;i++){u32off=i*16;u8type=bdir.r8(off);if(type<'A'||type>'Z')continue;if(bdir.r8(off+1)!=':')continue;if(trim_end_spaces(bdir.rstr(off+2,8))==name){returnstd::make_tuple(bdir,off);}}}returnstd::make_tuple(fsblk_t::block_t(),0xffffffff);}std::pair<std::error_condition,meta_data>vtech_impl::metadata(conststd::vector<std::string>&path){if(path.size()!=1)returnstd::make_pair(error::not_found,meta_data());auto[bdir,off]=file_find(path[0]);if(off==0xffffffff)returnstd::make_pair(error::not_found,meta_data());returnstd::make_pair(std::error_condition(),file_metadata(bdir.rodata()+off));}std::error_conditionvtech_impl::metadata_change(conststd::vector<std::string>&path,constmeta_data&meta){if(path.size()!=1)returnerror::not_found;auto[bdir,off]=file_find(path[0]);if(off==0xffffffff)returnerror::not_found;u8*entry=bdir.data()+off;if(meta.has(meta_name::file_type))entry[0x0]=meta.get_string(meta_name::file_type)[0];if(meta.has(meta_name::name)){std::stringname=meta.get_string(meta_name::name);name.resize(8,' ');wstr(entry+0x2,name);}if(meta.has(meta_name::loading_address)){u16new_loading=meta.get_number(meta_name::loading_address);u16new_end=get_u16le(entry+0xe)-get_u16le(entry+0xc)+new_loading;put_u16le(entry+0xc,new_loading);put_u16le(entry+0xe,new_end);}returnstd::error_condition();}std::pair<std::error_condition,std::vector<dir_entry>>vtech_impl::directory_contents(conststd::vector<std::string>&path){std::pair<std::error_condition,std::vector<dir_entry>>res;if(path.size()!=0){res.first=error::not_found;returnres;}res.first=std::error_condition();for(intsect=0;sect!=14;sect++){autobdir=m_blockdev.get(sect);for(u32i=0;i!=8;i++){u32off=i*16;u8type=bdir.r8(off);if(type<'A'||type>'Z')continue;if(bdir.r8(off+1)!=':')continue;meta_datameta=file_metadata(bdir.rodata()+off);res.second.emplace_back(dir_entry(dir_entry_type::file,meta));}}returnres;}std::error_conditionvtech_impl::rename(conststd::vector<std::string>&opath,conststd::vector<std::string>&npath){if(opath.size()!=1||npath.size()!=1)returnerror::not_found;auto[bdir,off]=file_find(opath[0]);if(off==0xffffffff)returnerror::not_found;std::stringname=npath[0];name.resize(8,' ');wstr(bdir.data()+off+2,name);returnstd::error_condition();}std::error_conditionvtech_impl::remove(conststd::vector<std::string>&path){returnerror::unsupported;}std::error_conditionvtech_impl::file_create(conststd::vector<std::string>&path,constmeta_data&meta){if(path.size()!=0)returnerror::not_found;// Find the key for the next unused entryfor(intsect=0;sect!=14;sect++){autobdir=m_blockdev.get(sect);for(u32i=0;i!=16;i++){u32off=i*16;u8type=bdir.r8(off);if(type!='T'&&type!='B'){std::stringfname=meta.get_string(meta_name::name,"");fname.resize(8,' ');bdir.w8(off+0x0,meta.get_string(meta_name::file_type,"T")[0]);bdir.w8(off+0x1,':');bdir.wstr(off+0x2,fname);bdir.w8(off+0xa,0x00);bdir.w8(off+0xb,0x00);bdir.w16l(off+0xc,meta.get_number(meta_name::loading_address,0x7ae9));bdir.w16l(off+0xe,bdir.r16l(off+0xc));// Size 0 initiallyreturnstd::error_condition();}}}returnerror::no_space;}std::pair<std::error_condition,std::vector<u8>>vtech_impl::file_read(conststd::vector<std::string>&path){std::vector<u8>data;if(path.size()!=1)returnstd::make_pair(error::not_found,data);auto[bdir,off]=file_find(path[0]);if(off==0xffffffff)returnstd::make_pair(error::not_found,data);constu8*entry=bdir.rodata()+off;u8track=entry[0xa];u8sector=entry[0xb];intlen=(get_u16le(entry+0xe)-get_u16le(entry+0xc))&0xffff;data.resize(len,0);intpos=0;while(pos<len){if(track>=40||sector>=16)break;autodblk=m_blockdev.get(track*16+sector);intsize=len-pos;if(size>126)size=126;memcpy(data.data()+pos,dblk.data(),size);pos+=size;track=dblk.r8(126);sector=dblk.r8(127);}returnstd::make_pair(std::error_condition(),data);}std::error_conditionvtech_impl::file_write(conststd::vector<std::string>&path,conststd::vector<u8>&data){if(path.size()!=1)returnerror::not_found;auto[bdir,off]=file_find(path[0]);if(off==0xffffffff)returnerror::not_found;u8*entry=bdir.data()+off;u32cur_len=(get_u16le(entry+0xe)-get_u16le(entry+0xc))&0xffff;u32new_len=data.size();if(new_len>65535)new_len=65535;u32cur_ns=(cur_len+125)/126;u32need_ns=(new_len+125)/126;// Enough space?if(cur_ns<need_ns&&free_block_count()<need_ns-cur_ns)returnerror::no_space;u8track=entry[0xa];u8sector=entry[0xb];std::vector<std::pair<u8,u8>>tofree;for(u32i=0;i!=cur_ns;i++){tofree.emplace_back(std::make_pair(track,sector));autodblk=m_blockdev.get(track*16+sector);track=dblk.r8(126);sector=dblk.r8(127);}free_blocks(tofree);std::vector<std::pair<u8,u8>>blocks=allocate_blocks(need_ns);for(u32i=0;i!=need_ns;i++){autodblk=m_blockdev.get(blocks[i].first*16+blocks[i].second);u32len=new_len-i*126;if(len>126)len=126;elseif(len<126)dblk.fill(0x00);memcpy(dblk.data(),data.data()+126*i,len);if(i<need_ns){dblk.w8(126,blocks[i+1].first);dblk.w8(127,blocks[i+1].second);}elsedblk.w16l(126,0);}u16end_address=(get_u16le(entry+0xc)+data.size())&0xffff;put_u16le(entry+0xe,end_address);if(need_ns){entry[0xa]=blocks[0].first;entry[0xb]=blocks[0].second;}elseput_u16le(entry+0xa,0);returnstd::error_condition();}std::vector<std::pair<u8,u8>>vtech_impl::allocate_blocks(u32count){std::vector<std::pair<u8,u8>>blocks;if(free_block_count()<count)returnblocks;autofmap=m_blockdev.get(15);for(u8track=1;track!=40;track++)for(u8sector=0;sector!=16;sector++){u32off=(track-1)*2+(sector/8);u32bit=1<<(sector&7);if(!(fmap.r8(off)&bit)){fmap.w8(off,fmap.r8(off)|bit);blocks.emplace_back(std::make_pair(track,sector));if(blocks.size()==count)returnblocks;}}abort();}voidvtech_impl::free_blocks(conststd::vector<std::pair<u8,u8>>&blocks){autofmap=m_blockdev.get(15);for(autoref:blocks){u8track=ref.first;u8sector=ref.second;u32off=(track-1)*2+(sector/8);u32bit=1<<(sector&7);fmap.w8(off,fmap.r8(off)&~bit);}}u32vtech_impl::free_block_count(){autofmap=m_blockdev.get(15);u32nf=0;for(u32off=0;off!=(40-1)*2;off++){u8m=fmap.r8(off);// Count 1 bits;m=((m&0xaa)>>1)|(m&0x55);m=((m&0xcc)>>2)|(m&0x33);m=((m&0xf0)>>4)|(m&0x0f);nf+=7-m;}returnnf;}