cc1541-4.2/0000777000000000000000000000000014567574547011011 5ustar rootrootcc1541-4.2/cc1541.sln0000644000000000000000000000377514567574546012436 0ustar rootroot Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cc1541", "cc1541.vcxproj", "{230D43EC-B2E8-447B-8605-D166B69FD94B}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_cc1541", "test_cc1541.vcxproj", "{4206D44B-8628-4E98-9F6F-775C8AC5E1B9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {230D43EC-B2E8-447B-8605-D166B69FD94B}.Debug|x64.ActiveCfg = Debug|x64 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Debug|x64.Build.0 = Debug|x64 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Debug|x86.ActiveCfg = Debug|Win32 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Debug|x86.Build.0 = Debug|Win32 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Release|x64.ActiveCfg = Release|x64 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Release|x64.Build.0 = Release|x64 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Release|x86.ActiveCfg = Release|Win32 {230D43EC-B2E8-447B-8605-D166B69FD94B}.Release|x86.Build.0 = Release|Win32 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Debug|x64.ActiveCfg = Debug|x64 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Debug|x64.Build.0 = Debug|x64 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Debug|x86.ActiveCfg = Debug|Win32 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Debug|x86.Build.0 = Debug|Win32 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Release|x64.ActiveCfg = Release|x64 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Release|x64.Build.0 = Release|x64 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Release|x86.ActiveCfg = Release|Win32 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal cc1541-4.2/test_cc1541.vcxproj.filters0000644000000000000000000000170114567574546016026 0ustar rootroot {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Source Files cc1541-4.2/transwarp v0.86.prg0000644000000000000000000001766714567574546014322 0ustar rootroot10140Qݍݥ ~f L b  x) JJMJJMJJIMݎݑ+ϩLM-ELwȱ. X$0x  MʆI H _r     D.Х`hh1&0 h ),`0XLТP gJP)jJJJ*)PKj^jJJJJ)jPЯ^j^jJJJ.HJJJhJJJȏ`2061L e++I^ ԍ ԍ,, 0b톻 ķIJk0+0,.$ӈ LNTRANSWARP'H0H1H +,hh010ي Y * w   @ 䝰 S h'hhhHw ,{L@A(A AV?"C9ZPTZ?<QP\?0EP<00EP 0DE 0PQ3<<DD 331DQ3 3<E<0D#@ 0 A@03 A000 ?ק,OdB -gEtr!G3ӮpCLLA ޥ O ސ Qbhhh H ޭH ޢh @߭H ޢ# @h ީKKHH. h)6 3 ސ. ަK.K ީ( ޽) ީ)D h@ @ސ Kб h`$ sʽHJJJJ h) 0:i,S,:, , ҄LU誩̈́}HOթӭp  z  ީ, ޭp z ߢ @ߩ @袪 Se ީX h` @ /߭. ީ0  ސI.`J ސ LOADING KB/S, RETRY RETRIES SLOWDOWNM-RYM-EM-W*:L x@ ,@0`LM-E Ў,x,0XM-RM-Exzĩj<L @`0Pp(UUUmݙ,PF0ȄVm4ᨵlm9@A l&z,  EeVm44lm99ᩄ w S?ݐ <ਐ E,P,p F&LLdL#1 #)ŨĆƶ𧊅eҩuVul()ܬܬy+y,Iݠ@lLllHH)8hh @ Fhh9h:L @hhא HIH8LMUUUmݙU U ȮU mݙUUUmșlF8, w F,0 ($TeV  $F0L E0$F( 椆LEFFLQ F橠8ݰ 8 䆰) (80LdFݦF0 ,000$F0 ,8<,,p  S,P,0L|0,%ܢ@ܢݎݢD 㘢 訢 xh h)ǍhhX`-ݍݢ` ݅9:8`,$ 橍 ܍ܢݠ 谗x xHHЎЭHļ F)א I0݈ܭ ЍiHƍVZ){JJJJ,ݍJJ)0ݠJJ)0ʭViݩd,0 bdLd ܢ_ܢܪʆѩݨ)J@ݰJJJ,ݍJJ)0ݠJJ)0­i됳 褝 ?ݐ&.ʊMݍMݍMݍMݍ #$LL, 2EF`ee,ȹi)@0YiQؠQآ5ݠ ?ݐL+$$8MjjMjjMjjM݌`Ć`ܬy񅬽y񅭠BԬܾԾԎ Ԏ` r0? r:LrLyKh$ 祺)ꤷ  r $ЦȈ  =J \HH hh k, ܩ , N- (Ԣ z($& a , LNхL) ,0 碂 ݢ о¢ S谨HJ Sxh`ݎݎݎݢQ܎܎ݎ`]ݝ``1܆0 ! 5K`),bl4 @Z,< S覐` >袥LF0Щ0ЭЍz`)I{Ў0`,0`08n` 'Q`)kn. ^L0,a ,0`P, !P,`P,PJJK `CC I sDF!F,)y*jkjkkޅFȘIDdECб&!pC.fFF|{IL  )0 HCL2{`"))XCq,ȹWYСhjl$&,.8:n02F46p8:T<>rt@BVDF;;;K0PhjXlnZp\ DH JvLNPxzR TFsLa|VXZ\~^` b"(d*fLKLN^rtv`xz|~JIS YYjYYjMEDF ***,0jjjjjj`, Fr ) PD-P+ HF΄PpHhHhpk;YPHyjkyjkyjXkyjL ~,t  M M M:-LaC : X0  Ťx;0ਈss)s7jL0L L;~ hjGb1E + "X0x&Ȑۍ`f X8LGహ:*?ɠ ҥ*ȅ z0{э,Xx,0Hh (M M (MΒ0 ***,0jjjjjj`dITJIWCDY*eD(܅ݢ &*ߐ88q`Qqc$Dd '' L cc1541-4.2/Makefile0000644000000000000000000000614214567574546012447 0ustar rootrootCC1541_CFLAGS = -std=c99 -pipe -O2 -Wall -Wextra -pedantic -Wno-error=date-time ALL_TARGETS = cc1541 ifneq ($(ENABLE_MAN),) ALL_TARGETS += man endif ifneq ($(ENABLE_WERROR),) CC1541_CFLAGS += -Werror endif override CFLAGS := $(CC1541_CFLAGS) $(CFLAGS) prefix ?= /usr/local bindir ?= $(prefix)/bin mandir ?= $(prefix)/share/man INSTALL ?= install VERSION := $(shell grep 'define VERSION' cc1541.c | cut -d\" -f2) CC1541_SRC := Makefile transwarp\ v0.84.prg transwarp\ v0.86.prg $(wildcard *.c *.h *.in *.sln *.vcxproj* LICENSE* README*) all: $(ALL_TARGETS) cc1541.o: cc1541.c cc1541: cc1541.o cc1541.1.txt: cc1541.1.txt.in sed -e "s/@@VERSION@@/$(VERSION)/g" < cc1541.1.txt.in > cc1541.1.txt cc1541.1: cc1541.1.txt a2x -d manpage -f manpage cc1541.1.txt man: cc1541.1 test_cc1541.o: test_cc1541.c test_cc1541: test_cc1541.o check: cc1541 test_cc1541 ./test_cc1541 ./cc1541 test: check cc1541_asan: cc1541.c $(CC) -g -fsanitize=address -fsanitize=undefined -o $@ $< asantest: cc1541_asan test_cc1541 ./test_cc1541 ./cc1541_asan install: all $(INSTALL) -Dpm 0755 ./cc1541 $(DESTDIR)$(bindir)/cc1541 ifneq ($(ENABLE_MAN),) $(INSTALL) -Dpm 0644 ./cc1541.1 $(DESTDIR)$(mandir)/man1/cc1541.1 endif cc1541-$(VERSION).tar: $(CC1541_SRC) rm -rf cc1541-$(VERSION)/ *~ README.md.T $(eval $@_TMP := $(shell mktemp -d)) mkdir -p $($@_TMP)/cc1541-$(VERSION) cp -a $(CC1541_SRC) $($@_TMP)/cc1541-$(VERSION)/ chmod 0644 $($@_TMP)/cc1541-$(VERSION)/* tar cf cc1541-$(VERSION).tar -C$($@_TMP) cc1541-$(VERSION)/ rm -rf $($@_TMP) cc1541-$(VERSION).tar.bz2: cc1541-$(VERSION).tar bzip2 -9cz < cc1541-$(VERSION).tar > cc1541-$(VERSION).tar.bz2 cc1541-$(VERSION).tar.gz: cc1541-$(VERSION).tar gzip -9c < cc1541-$(VERSION).tar > cc1541-$(VERSION).tar.gz cc1541-$(VERSION).tar.xz: cc1541-$(VERSION).tar xz -ce < cc1541-$(VERSION).tar > cc1541-$(VERSION).tar.xz cc1541-$(VERSION).zip: $(CC1541_SRC) rm -rf cc1541-$(VERSION)/ *~ README.md.T $(eval $@_SRC := $(shell pwd)) $(eval $@_TMP := $(shell mktemp -d)) mkdir -p $($@_TMP)/cc1541-$(VERSION) cp -a $(CC1541_SRC) $($@_TMP)/cc1541-$(VERSION)/ chmod 0644 $($@_TMP)/cc1541-$(VERSION)/* cd $($@_TMP) && \ zip -9qr $($@_SRC)/cc1541-$(VERSION).zip cc1541-$(VERSION)/ rm -rf $($@_TMP) dist-bz2: cc1541-$(VERSION).tar.bz2 dist-gz: cc1541-$(VERSION).tar.gz dist-xz: cc1541-$(VERSION).tar.xz dist-zip: cc1541-$(VERSION).zip dist-all: dist-bz2 dist-gz dist-xz dist-zip dist: dist-gz dist-zip dist-check: dist $(eval $@_TMP := $(shell mktemp -d)) mkdir -p $($@_TMP) tar xf cc1541-$(VERSION).tar.gz -C $($@_TMP) $(MAKE) -C $($@_TMP)/cc1541-$(VERSION)/ all check rm -rf $($@_TMP) codestyle: cc1541.c test_cc1541.c astyle --style=kr -n -s -z2 cc1541.c test_cc1541.c wrap: cc1541.1.txt.in LICENSE.txt README.md for f in $^; do \ fold -s -w 76 < $$f | perl -pe 's/[\t\040]+$$//' > $$f.T; \ mv -f $$f.T $$f; \ done clean: rm -rf cc1541-$(VERSION)/ *~ README.md.T *.o *.orig cc1541 cc1541_asan test_cc1541 cc1541-$(VERSION).* cc1541.1 cc1541.1.txt .PHONY: all check clean codestyle dist dist-all dist-bz2 dist-check dist-gz dist-xz dist-zip install man test wrap .EXPORT_ALL_VARIABLES: cc1541-4.2/README.md0000644000000000000000000002032514567574546012265 0ustar rootroot# README # Note: You can find a more extensive html documentation here: https://acoustic-velocity.com/cc1541 This is cc1541, a tool for creating Commodore 1541 Floppy disk images in D64, D71 or D81 format with custom sector interleaving etc. Also supports extended tracks 35-40 using either SPEED DOS or DOLPHIN DOS BAM-formatting. Furthermore supports writing Transwarp disk images for the fantastically fast loader by Krill. Originally written by JackAsser, with improvements and fixes by Krill, some more improvements by Claus and further improvements by Björn Esser. The program is provided under the terms of the MIT license, please refer to the included LICENSE.txt file for its terms and conditions. The public source code repository can be found here: https://bitbucket.org/PTV_Claus/cc1541/src/master/ ## Usage examples ## * "cc1541 -h" shows the command line help * "cc1541 image.d64" lists the content of image.d64 * "cc1541 -f program -w program.prg image.d64" adds the file program.prg to image.d64 (or creates the image if it does not exist yet) using the name "program" * "cc1541 -f program1 -w program1.prg -f program2 -w program2.prg image.d64" adds two files under the names program1 and program2 * "cc1541 -s 4 -f program -w program.prg image.d64" writes a file with a dedicated sector interleave for a fastloader (the best value depends on the used fastloader and its configuration) * "cc1541 -f game -W game.prg -f 'transwarp v0.86' -w transwarp.prg Transwarp-Game.d64" creates a Transwarp disk image with the required boot file. * "cc1541 -T DEL -f ---------------- -L image.d64" creates a DEL entry as separator. ## Version history ## v4.2 * -X switch added to allow extraction of files from images. For Transwarp files, the transwarp boot loader will be extracted instead. * -p switch added to write arbitrary data directly at a certain place on the disk. * -N now implies -m to avoid duplicate hash errors per default. * -l now allows to use wildcards * and ?. * Per default, complete track 53 is reserved for D71 now, just as in the original format routine. This can be overriden by -t during the creation of the D71 image. Thanks to Martin Thierer for the help! * Hex escapes now allow both upper case and lower case. * In verbose mode, filenames are printed with escape characters now. * -a switch avoids hex escapes now where possible. * Bugfix: The display of free blocks was utterly wrong for D71, thanks to Martin Thierer for the fix! * Bugfix: Validate did not work for D71, thanks to Martin Thierer for the fix! It also works for D81 now. * Bugfix: -c resulted in "disk full" error for non-D71, it is now rejected instead. * Bugfix: validation would complain about looped t/s chains when it was actually a collision between different t/s chains. * Bugfix: the default disk id "00 2a" now has a shift-space in the middle, just as in the original format routine. v4.1 * Per default, Transwarp files will now be allocated such that less disk space is wasted. Old behaviour can still be enabled with -A 0. * Commandline options -s and -b are now rejected for Transwarp files instead of being silently ignored. * Unicode printout can now be configured with environment variable CC1541UNICODE, using same values as for -U switch. * Bugfix: the last allocated track for Transwarp files was not checked to be free, sometimes resulting in unexpected write errors. * Bugfix: mixing Transwarp and normal files would often leave parts of the disk unallocated, so that less data than expected would fit onto a disk. * Bugfix: verbose file allocation printout would sometimes print garbage for filenames or block numbers v4.0 * The default handling for large tail gaps has been removed, as it does not provide any advantage and was only there to be as close as possible to CBM DOS behaviour. In fact, it actually slows down fast loaders. This warrants a major version number increase. * -H switch added to set BAM messages at BAM offset $ab * -F with negative values now specifies a track skew * -B cannot be used with Transwarp files anymore, as the loader relies on correct block sizes in the directory * Bugfix: the BAM allocation for SPEED DOS and DOLPHIN DOS was wrong (mixed up between the two and also shifted by 4 bytes) * Bugfix: Data from unused blocks could leak into the last block of a new file behind the actual file data v3.4 * Support for Transwarp 0.86 and later * Commandline option -R added for restoring deleted or formatted files * -L switch added to create directory entries without writing a file * -a switch added to print a cc1541 command line that re-creates the directory art of the given disk image * -T now also allows a number as parameter * Files with file type DEL will now actually be written, use -L if you do not want that * Improved verbose allocation printout * More robustness against invalid t/s links * Added support for underscore in PETSCII filenames * Bugfix: no endless loops for cyclic directory chains anymore v3.3 * Transwarp encoding added * Verbose mode displays file allocation on unmodified images * Optimised non-standard interleave scheme * Added unicode printout option for PETSCII, special thanks to Jonas Minnberg for helping with Linux support and inverse characters! * Output file is not touched when no change is requested * Bugfix: Fixed crash on write protected output file * Bugfix: Spacing in directory printout was not correct when file size is more than 999 blocks v3.2 * -N switch added to allow duplicate file names * Trying to create loop file with the same name as the referenced file now throws an error, if not -N is also specified * Changed default next file first sector on new track to 0 to align with Commodore DOS * Interleave violations are now shown in block allocation printout * Block allocation for loop files is not displayed anymore when referenced file was also written * Bugfix: Fixed file index printout for block allocation * Bugfix: Correct interleave is now displayed for loop files when referenced file was also written v3.1 * Bugfix: overwriting files did not work correctly when there was a free slot in the directory before the file to overwrite * Bugfix: Filetype, open and protected flags were not reset after writing a file * Bugfix: default disk name and id were wrong in command line help v3.0 * ASCII to PETSCII conversion added, this breaks backward compatibility (and therefore warrants a major version increase)! * Support for D81 images * Default printout is now a full directory similar to how it would be displayed on a Commodore machine * -v switch added for verbose output of file and block allocation * -M switch added to specify number of characters for filename hash calculation for latest Krill loader * -m switch added to skip filename collision check * -B switch added to allow setting the displayed file size * -o switch added to prevent overwriting of existing files on an image * -V switch added to validate images before editing them * -T switch added to allow setting the file type * -O switch added to allow setting the open flag * -P switch added to allow setting the protected flag * Hex escapes are now also allowed for disk name and ID * When no disk file name is provided, only the base name of the input file is used as disk file name instead of the full path * Bugfix: fixed memory access issue for filenames with 16 letters * Bugfix: fixed endless loop when reaching track 53 on a D71 * Bugfix: fixed that (shadow) directory track would be used for data when the shadow directory is a neighbour of the actual directory * Bugfix: G64 output is now an optional additional output using -g, avoiding the utterly broken reading of G64 files * Bugfix: loop files have actual file size per default instead of 0 * Bugfix: printouts to stderr and stdout are more consistent now v2.0 * The first version with a release number * All existing modifications consolidated (hopefully) * G64 output dependent on output file name instead of a source code define * Converted to ANSI C99 * MSVC build files added * getopt removed * Simple test suite added * Bugfix: hex escape was not considered for file overwrite detection * Bugfix: first sector per track was ignored for track 1 * Bugfix: default sector interleave was ignored for first file cc1541-4.2/transwarp v0.84.prg0000644000000000000000000001770014567574546014304 0ustar rootroot10149Qݍݥ ~f L ԍ ԍԤb  x) JJMJJMJJIMݎݑ+ϩLM-ELwȱ. X$0x  ʆI H _r  D.а`hh1&0 h ),`0XLТP gJP)jJJJ*)PKj^jJJJJ)jPЯ^j^jJJJ.HJJJhJJJȏ`2061L e++Ik, 0b톻 ķ=>x00,.$^ӈ LY@A(A AV?TRANSWARPݎ'H0H1H +,hh010ي Y * w   @ M䝰  S h'hhhHw ,{L"C9ZPTZ?<QP\?0EP<00EP 0DE 0PQ3<<DD 331DQ3 3<E<0D#@ 0 A@03 A000 ?ק,OdB -gEtr!G3ӮpCLLAH \JYJ fۢXYZ[Z\H fhYi@ȐhiY&m*ţ6mYHXYhX`JI8GHÅGeąHe8GޥHޥH АFX8G@ HUVKʆW..*KK8niߐ򩎪$0`,厧ᎳLHD.#0 ׍;LIJʊ @, ȄDh BCP`@A8>>??FP$O0O$O0 M0OM`B@A<8HbGb HYbYĮEF$aHH hR J A _ީ- ޢp _h,:' ީ j޺iH j hH ޽ jhx`SH l ޥ z ސ |h2$,%'H ޥH hh' ީ$ H ޭH ޢh A ޭH ޢ! Ah ީKKI/H h)7 ' ސ/ ަK/K ީ( ޽) ީ)EH h@ 4ސ Kа h`$ hʵ?HJJJJ uh) i0,i6,S,:, , ҄"L窩̈́"}HOթӭp z  ީ, ީp z ߢ Aߩ nHh ީX h` n 0߭. ީ0  ސI.`K ސ LOADING KB/S, RETRY RETRIES SLOWDOWNM-RYM-EM-W*:L x@ ,@0`LM-E Ў,x,0XM-RM-ExzjБ(]]]mݙ,PO0RYȄRnm ᨵmUVUV !l&=,,p E ,P,0KLX0,H hD 墺 I %@_h: h h)ǍhhX`|!?ݩ-ݍݎݎ` % ܍ܩ,$ ݎݢ 婍q( I簕x4 ' HЭHТV煐F)א I0݈H ЍHV)JJJJ,ݍJJ)0ݠJJ)0ݩĬй;HI_ ;DXd0 I fbd $ Lݢ fL܎ʎܩ_ܢ I$ . ?ݐ$ ݎݎ܎ܠ m$XLL+EE,EE JMKKnKNO`e>>e??ip7)Sq8T@0YiRQSؠQSآ5RQݠ ?ݐLu$$8MjjMjjMjjM݌`ܬyyBԬܾԾԎ Ԏ`hK)w In HH Shh , ܩ , N- ( Ԣ (0 ( I煐LNх"#L$% a ,0Hf Ih Х ~簕HJķ ~xh`ݍݍݍݭ) Q`%ܩ@ -ݍݩ8Hݪݨݍh`C܆"#0 W 5K`b4 nZ,< ~禐`"#LF y, s n,ė"0Щ0ЭЍz`)I{Ў0`,0` 'Q`  ~,0Hh M M Mφs;LQq#Cc$DdqdITNIWGDYeD(܅ݢ &*ߐ88qڅ` I1צּ`EE qHچ!>~` hjT(W!art@BVDF;;;K0PhjXlnZp\ DH JvLNPxzR TFsL`|VXZ\~^` b"(d*fLKLN^rtv`xz|~JIS YYjYYjMEDF ***,0jjjjjj`, Fr ( PD-P+ GF΄PpHhHhpk;YPHyjkyjkyjXkyjLLUL08n`HH)8'hh @ XDFX2llXh4hLhh9h:L, @l l @`0PpHIHLIJI`L  0l '' L cc1541-4.2/cc1541.vcxproj0000644000000000000000000001632314567574546013326 0ustar rootroot Debug Win32 Release Win32 Debug x64 Release x64 {230D43EC-B2E8-447B-8605-D166B69FD94B} Win32Proj cc1541 10.0 Application true v142 Unicode Application false v142 true Unicode Application true v142 Unicode Application false v142 true Unicode true true $(Platform)\$(ProjectName)\$(Configuration)\ false false $(Platform)\$(ProjectName)\$(Configuration)\ Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 MaxSpeed true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true Level3 MaxSpeed true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true cc1541-4.2/cc1541.c0000644000000000000000000062724114567574546012064 0ustar rootroot/******************************************************************************* * Copyright (c) 2008-2024 JackAsser, Krill, Claus, Björn Esser * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ #define VERSION "4.2" #define _CRT_SECURE_NO_WARNINGS /* avoid security warnings for MSVC */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif #define min(a, b) (((a) < (b)) ? (a) : (b)) #ifdef _WIN32 #include #define FILESEPARATOR '\\' #else #define FILESEPARATOR '/' #endif #define DIRENTRIESPERBLOCK 8 #define DIRTRACK_D41_D71 18 #define DIRTRACK_D81 40 #define SECTORSPERTRACK_D81 40 #define MAXNUMFILES_D81 ((SECTORSPERTRACK_D81 - 3) * DIRENTRIESPERBLOCK) #define DIRENTRYSIZE 32 #define BLOCKSIZE 256 #define BLOCKOVERHEAD 2 #define TRACKLINKOFFSET 0 #define SECTORLINKOFFSET 1 #define FILETYPEOFFSET 2 #define FILETYPEDEL 0 #define FILETYPESEQ 1 #define FILETYPEPRG 2 #define FILETYPEUSR 3 #define FILETYPEREL 4 #define FILETYPETRANSWARPMASK 0x100 #define FILETRACKOFFSET 3 #define FILESECTOROFFSET 4 #define FILENAMEOFFSET 5 #define FILENAMEMAXSIZE 16 #define FILENAMEEMPTYCHAR (' ' | 0x80) #define BAMMESSAGEOFFSET 0xab #define BAMMESSAGEMAXSIZE 0x100-BAMMESSAGEOFFSET #define TRANSWARPSIGNATROFFSLO 21 #define TRANSWARPSIGNATURELO 'T' #define TRANSWARPSIGNATROFFSHI 22 #define TRANSWARPSIGNATUREHI 'W' #define DIRDATACHECKSUMOFFSET 23 #define TRANSWARPTRACKOFFSET 24 #define FILECHECKSUMOFFSET 25 #define LOADADDRESSLOOFFSET 26 #define LOADADDRESSHIOFFSET 27 #define ENDADDRESSLOOFFSET 28 #define ENDADDRESSHIOFFSET 29 #define FILEBLOCKSLOOFFSET 30 #define FILEBLOCKSHIOFFSET 31 #define D64NUMBLOCKS (664 + 19) #define D64SIZE (D64NUMBLOCKS * BLOCKSIZE) #define D64SIZE_EXTENDED (D64SIZE + 5 * 17 * BLOCKSIZE) #define D71SIZE (D64SIZE * 2) #define D81SIZE (D81NUMTRACKS * SECTORSPERTRACK_D81 * BLOCKSIZE) #define D64NUMTRACKS 35 #define D64NUMTRACKS_EXTENDED (D64NUMTRACKS + 5) #define D71NUMTRACKS (D64NUMTRACKS * 2) #define D81NUMTRACKS 80 #define BAM_OFFSET_SPEED_DOS 0xc0 #define BAM_OFFSET_DOLPHIN_DOS 0xac #define DIRSLOTEXISTS 0 #define DIRSLOTFOUND 1 #define DIRSLOTNOTFOUND 2 /* for sector chain analysis */ #define UNALLOCATED 0 /* unused as of now */ #define ALLOCATED 1 /* part of a valid sector chain */ #define FILESTART 2 /* analysed to be the start of a sector chain */ #define FILESTART_TRUNCATED 3 /* analysed to be the start of a sector chain, was truncated */ #define POTENTIALLYALLOCATED 4 /* currently being analysed */ /* error codes for sector chain validation */ #define VALID 0 /* valid chain */ #define ILLEGAL_TRACK 1 /* ends with illegal track pointer */ #define ILLEGAL_SECTOR 2 /* ends with illegal sector pointer */ #define LOOP 3 /* loop in current chain */ #define COLLISION 4 /* collision with other file */ #define CHAINED 5 /* ends at another file start */ #define CHAINED_TRUNCATED 6 /* ends at start of a truncated file */ #define FIRST_BROKEN 7 /* issue already in first sector */ /* undelete levels */ #define RESTORE_DIR_ONLY 0 /* Only restore all dir entries without touching any t/s links */ #define RESTORE_VALID_FILES 1 /* Fix dir entries for files with valid t/s chains */ #define RESTORE_VALID_CHAINS 2 /* Also add wild sector chains with valid t/s chains */ #define RESTORE_INVALID_FILES 3 /* Also fix dir entries with invalid t/s chains */ #define RESTORE_INVALID_CHAINS 4 /* Also add and fix wild invalid t/s chains */ #define RESTORE_INVALID_SINGLES 5 /* Also include single block files */ /* error codes for directory */ #define DIR_OK 0 #define DIR_ILLEGAL_TS 1 #define DIR_CYCLIC_TS 2 #define TRANSWARP "TRANSWARP" #define TRANSWARPBASEBLOCKSIZE 0xc0 #define TRANSWARPBUFFERBLOCKSIZE 0x1f #define TRANSWARPBLOCKSIZE (TRANSWARPBASEBLOCKSIZE + TRANSWARPBUFFERBLOCKSIZE) #define TRANSWARPKEYSIZE 29 /* 232 bits */ #define TRANSWARPKEYHASHROUNDS 33 /* maximum number of files for transwarp order permutation */ #define MAXPERMUTEDFILES 11 /* maximum number of free sectors on an extended D64 */ #define MAXFREESECTORS 768 /* maximum number of patches */ #define MAXNUMPATCHES 32 /* maximum number of patterns for file extraction */ #define MAXNUMEXTRACTIONS 32 /* Table for conversion of uppercase PETSCII to Unicode */ static unsigned int p2u_uppercase_tab[256] = { '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', ' ', '!', '\"', '#', '$', '%', '&', 0x2019, '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', 0xa3, ']', 0x2191, 0x2190, 0x1fb79, 0x2660, 0x1fb72, 0x1fb78, 0x1fb77, 0x1fb76, 0x1fb7a, 0x1fb71, 0x1fb74, 0x256e, 0x2570, 0x256f, 0x1fb7c, 0x2572, 0x2571, 0x1fb7d, 0x1fb7e, 0x25cf, 0x1fb7b, 0x2665, 0x1fb70, 0x256d, 0x2573, 0x25cb, 0x2663, 0x1fb75, 0x2666, 0x253c, 0x1fb8c, 0x2502, 0x3c0, 0x25e5, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', ' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x25e4, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581, 0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x2590, 0x2594, 0x2580, 0x2583, 0x1fb7f, 0x2596, 0x259d, 0x2518, 0x2598, 0x259a, 0x2500, 0x2660, 0x1fb72, 0x2500, 0x1fb77, 0x1fb76, 0x1fb7a, 0x1fb71, 0x1fb74, 0x256e, 0x2570, 0x256f, 0x1fb7c, 0x2572, 0x2571, 0x1fb7d, 0x1fb7e, 0x25cf, 0x1fb7b, 0x2665, 0x1fb70, 0x256d, 0x2573, 0x25cb, 0x2663, 0x1fb75, 0x2666, 0x253c, 0x1fb8c, 0x2502, 0x3c0, 0x25e5, ' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x25e4, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581, 0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x1fb88, 0x1fb82, 0x1fb83, 0x2583, 0x1fb7f, 0x2596, 0x259d, 0x2518, 0x2598, 0x3c0 }; /* Table for conversion of lowercase PETSCII to Unicode */ static unsigned int p2u_lowercase_tab[256] = { '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', ' ', '!', '\"', '#', '$', '%', '&', 0x2019, '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', 0xa3, ']', 0x2191, 0x2190, 0x1fb79, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0x253c, 0x1fb8c, 0x2502, 0x1fb95, 0x1fb98, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', ' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x1fb99, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581, 0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x2590, 0x2594, 0x2580, 0x2583, 0x1fb7f, 0x2713, 0x259d, 0x2518, 0x2598, 0x259a, 0x2500, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0x253c, 0x1fb8c, 0x2502, 0x1fb95, 0x1fb98, ' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x1fb99, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581, 0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x1fb88, 0x1fb82, 0x1fb83, 0x2583, 0x2713, 0x2596, 0x259d, 0x2518, 0x2598, 0x1fb96 }; typedef struct { const unsigned char* alocalname; /* local file name or name of loop file in ASCII */ unsigned char plocalname[FILENAMEMAXSIZE]; /* loop file in PETSCII */ unsigned char pfilename[FILENAMEMAXSIZE]; /* disk file name in PETSCII */ int direntryindex; int direntrysector; int direntryoffset; int sectorInterleave; int first_sector_new_track; int track; int sector; int nrSectors; int nrSectorsShown; int filetype; int mode; int force_new; int size; int last_track; bool have_key; unsigned char key[TRANSWARPKEYSIZE]; } imagefile; typedef struct { const char* filename; int track; int sector; int offset; } patch; enum mode { MODE_BEGINNING_SECTOR_MASK = 0x003f, /* 6 bits */ MODE_MIN_TRACK_MASK = 0x0fc0, /* 6 bits */ MODE_MIN_TRACK_SHIFT = 6, MODE_SAVETOEMPTYTRACKS = 0x1000, MODE_FITONSINGLETRACK = 0x2000, MODE_SAVECLUSTEROPTIMIZED = 0x4000, MODE_LOOPFILE = 0x8000, MODE_TRANSWARPBOOTFILE = 0x10000, MODE_NOFILE = 0x20000 }; typedef enum { IMAGE_D64, IMAGE_D64_EXTENDED_SPEED_DOS, IMAGE_D64_EXTENDED_DOLPHIN_DOS, IMAGE_D71, IMAGE_D81 } image_type; static const char *filetypename_uc[] = { "DEL", "SEQ", "PRG", "USR", "REL", "CBM", "???", "???", "???", "???", "???", "???", "???", "???", "???", "???" }; static const char *filetypename_lc[] = { "del", "seq", "prg", "usr", "rel", "cbm", "???", "???", "???", "???", "???", "???", "???", "???", "???", "???" }; static const char *error_name[] = { "no error", "illegal track", "illegal sector", "loop", "collision", "chained", "chained", "first block broken" }; static const char *dir_error_string[] = { "no error", "illegal track or sector in directory sector chain", "cycle in directory sector chain" }; static const int sectors_per_track[] = { /* 1-17 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, /* 18-24 */ 19,19,19,19,19,19,19, /* 25-30 */ 18,18,18,18,18,18, /* 31-35 */ 17,17,17,17,17, /* 36-52 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, /* 53-59 */ 19,19,19,19,19,19,19, /* 60-65 */ 18,18,18,18,18,18, /* 66-70 */ 17,17,17,17,17 }; static const int sectors_per_track_extended[] = { /* 1-17 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, /* 18-24 */ 19,19,19,19,19,19,19, /* 25-30 */ 18,18,18,18,18,18, /* 31-35 */ 17,17,17,17,17, /* 36-40 */ 17,17,17,17,17, /* 41-57 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, /* 58-64 */ 19,19,19,19,19,19,19, /* 65-70 */ 18,18,18,18,18,18, /* 71-75 */ 17,17,17,17,17, /* 76-80 */ 17,17,17,17,17 }; static int quiet = 0; /* global quiet flag */ static int verbose = 0; /* global verbose flag */ static int num_files = 0; /* number of files to be written provided by the user */ static int max_hash_length = 16; /* number of bytes of the filenames to calculate the hash over */ static int unicode = 0; /* which unicode mapping to use: 0 = none, 1 = upper case, 2 = lower case */ static int modified = 0; /* image needs to be written */ static int dir_error = DIR_OK; /* directory has an error */ /* Prints the command line help */ static void usage(void) { printf("\n*** This is cc1541 version " VERSION " built on " __DATE__ " ***\n\n"); printf("Usage: cc1541 [options] image.[d64|d71|d81]\n\n"); printf("Disk Related:\n"); printf("============\n"); printf("-n diskname Disk name, default='cc1541'.\n"); printf("-i id Disk ID, default='00 2a'.\n"); printf("-4 Use tracks 35-40 with SPEED DOS BAM formatting.\n"); printf("-5 Use tracks 35-40 with DOLPHIN DOS BAM formatting.\n"); printf("-d track Maintain a shadow directory (copy of the actual directory without\n"); printf(" a valid BAM).\n"); printf("-g filename Write additional g64 output file with given name.\n"); printf("-H message Hidden BAM message. Only for D64 (up to 85 chars) or SPEED DOS\n"); printf(" (up to 20 chars).\n"); printf("-p t,s,o,file Write binary patch from file to track t, sector s, offset o.\n"); printf("-V Do not modify image unless it is in valid CBM DOS format.\n\n"); printf("File Related:\n"); printf("============\n"); printf("-w localname Write local file to disk, if filename is not set then the\n"); printf(" local name is used. After file written, the filename is unset.\n"); printf("-f filename Use filename as name when writing next file, use prefix # to\n"); printf(" include arbitrary PETSCII characters (e.g. -f \"start#a0,8,1\").\n"); printf("-T filetype Filetype for next file, allowed parameters are PRG, SEQ, USR, REL\n"); printf(" and DEL, or a decimal number between 0 and 255. Default is PRG.\n"); printf("-P Set write protect flag for next file.\n"); printf("-O Set open flag for next file.\n"); printf("-B numblocks Write the given value as file size in blocks to the directory for\n"); printf(" the next file. Not applicable for Transwarp files.\n"); printf("-L Add dir entry without writing file (track and sector will be 0),\n"); printf(" requires a filename given with -f.\n"); printf("-l filename Write loop file (an additional dir entry) to existing file to\n"); printf(" disk. Wildcards * and ? are supported. Set filename with -f.\n"); printf("-N Force creation of a new directory entry, even if a file with the\n"); printf(" same name exists already.\n"); printf("-o Do not overwrite if file with same name exists already.\n"); printf("-X filename Extract file from disk image. Wildcards * and ? are supported.\n\n"); printf("Track/Sector Control:\n"); printf("====================\n"); printf("-S value Default sector interleave, default=10. Not applicable for D81.\n"); printf("-s value Next file sector interleave, valid after each file.\n"); printf(" The interleave value falls back to the default value set by -S\n"); printf(" after the first sector of the next file. Not applicable for D81\n"); printf(" or Transwarp files.\n"); printf("-e Start next file on an empty track (default start sector is\n"); printf(" current sector plus interleave).\n"); printf("-E Try to fit file on a single track.\n"); printf("-r track Restrict next file blocks to the specified track or higher.\n"); printf("-b sector Set next file beginning sector to the specified value.\n"); printf(" Not applicable for D81 or Transwarp files.\n"); printf("-F value Next file first sector on a new track (default=0).\n"); printf(" Any negative value assumes aligned tracks and uses current\n"); printf(" sector + interleave - value. After each file, the value falls\n"); printf(" back to the default. Not applicable for D81.\n"); printf("-t Use directory track to also store files (makes -x useless)\n"); printf(" (default no).\n"); printf("-u numblocks When using -t, amount of dir blocks to leave free (default=2).\n"); printf("-x Don't split files over directory track hole (default split files).\n"); printf("-c Save next file cluster-optimized (d71 only).\n\n"); printf("Fastloader Specific:\n"); printf("===================\n"); printf("-M numchars Hash computation maximum filename length, this must\n"); printf(" match loader option FILENAME_MAXLENGTH in Krill's loader.\n"); printf(" Default is 16.\n"); printf("-m Ignore filename hash collisions, without this switch a collision\n"); printf(" results in an error.\n"); printf("-W localname Like -w, but encode file in Transwarp format.\n"); printf(" Provide Transwarp bootfile as last file using\n"); printf(" \"-f 'transwarp vX.YZ' -w 'transwarp vX.YZ.prg'\"\n"); printf("-A value Select allocation strategy for Transwarp files (default 1).\n"); printf(" 0: allocate in the order given on the commandline.\n"); printf(" 1: save disk space by reordering transwarp files.\n"); printf("-K key Set an encryption key for Transwarp files, a string of up to 29\n"); printf(" characters.\n\n"); printf("Miscellaneous:\n"); printf("=============\n"); printf("-h Print this command line help.\n"); printf("-v Be verbose.\n"); printf("-q Be quiet.\n"); printf("-U mapping Print PETSCII as Unicode (requires Unicode 13.0 font, e.g.\n"); printf(" UNSCII). Use mapping 0 for ASCII output, 1 for upper case, 2 for\n"); printf(" lower case, default is 0.\n"); printf("-R level Try to restore deleted and formatted files.\n"); printf(" level 0: Only restore dir entries without touching any t/s links.\n"); printf(" level 1: Fix dir entries for files with valid t/s chains.\n"); printf(" level 2: Also add wild sector chains with valid t/s chains.\n"); printf(" level 3: Also fix dir entries with invalid t/s chains.\n"); printf(" level 4: Also add and fix wild invalid t/s chains.\n"); printf(" level 5: Also add reasonable wild single blocks.\n"); printf("-a Print command line options that would create the same directory as\n"); printf(" the one in the given image (for directory art import).\n"); printf("\n"); printf("Find a more extensive online documentation here:\n"); printf("https://acoustic-velocity.com/cc1541\n"); printf("\n"); exit(-1); } /* Returns a pointer to the filename in a path */ static const unsigned char* basename(const unsigned char* path) { const unsigned char* name; (name = (unsigned char*)strrchr((char *)path, FILESEPARATOR)) ? ++name : (name = path); return name; } /* Calculates a hash from a filename to be used by Krill's loader */ static unsigned int filenamehash(const unsigned char *filename) { int pos = min(max_hash_length, (int)strlen((char *)filename)); while ((pos > 0) && (((unsigned char) filename[pos - 1]) == FILENAMEEMPTYCHAR)) { --pos; } unsigned char hashlo = pos; unsigned char hashhi = pos; int carry = 1; for (int i = pos - 1; i >= 0; --i) { unsigned int sum = hashlo + filename[i] + carry; carry = (sum >= 256) ? 1 : 0; sum &= 0xff; hashlo = sum; sum += hashhi + carry; carry = (sum >= 256) ? 1 : 0; hashhi = sum; } return (hashhi << 8) | hashlo; } /* Returns the size of an image in bytes */ static unsigned int image_size(image_type type) { switch (type) { case IMAGE_D64: return D64SIZE; case IMAGE_D64_EXTENDED_SPEED_DOS: /* fall through */ case IMAGE_D64_EXTENDED_DOLPHIN_DOS: return D64SIZE_EXTENDED; case IMAGE_D71: return D71SIZE; case IMAGE_D81: return D81SIZE; default: return 0; } } /* Returns the number of tracks in an image */ static unsigned int image_num_tracks(image_type type) { switch (type) { case IMAGE_D64: return D64NUMTRACKS; case IMAGE_D64_EXTENDED_SPEED_DOS: /* fall through */ case IMAGE_D64_EXTENDED_DOLPHIN_DOS: return D64NUMTRACKS_EXTENDED; case IMAGE_D71: return D71NUMTRACKS; case IMAGE_D81: return D81NUMTRACKS; default: return 0; } } /* Returns the number of sectors for a given track */ static int num_sectors(image_type type, int track) { return (type == IMAGE_D81) ? SECTORSPERTRACK_D81 : (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) ? sectors_per_track_extended[track - 1] : sectors_per_track[track - 1]); } /* Returns the number of blocks in an image */ static unsigned int image_num_blocks(image_type type) { int num_blocks = 0; for (unsigned int t = 1; t <= image_num_tracks(type); t++) { num_blocks += num_sectors(type, t); } return num_blocks; } /* Returns the directory track of an image */ static int dirtrack(image_type type) { return (type == IMAGE_D81) ? DIRTRACK_D81 : DIRTRACK_D41_D71; } /* Converts an ASCII character to PETSCII */ static unsigned char a2p(unsigned char a) { switch (a) { case '\n': return 0x0d; case '_': return 0xa4; case 0x7e: return 0xff; default: if ((a >= 0x5b) && (a < 0x5f)) { return a; } if ((a >= 0x60) && (a <= 0x7e)) { return a ^ 0x20; } if ((a >= 'A') && (a <= 'Z')) { return a | 0x80; } return a; } } /* Converts a PETSCII character to ASCII */ static unsigned char p2a(unsigned char p) { switch (p) { case 0x0a: case 0x0d: return '\n'; case 0x40: case 0x60: return p; case 0xa0: case 0xe0: return ' '; case 0xa4: case 0xe4: return '_'; default: switch (p & 0xe0) { case 0x40: case 0x60: p ^= 0x20; break; case 0xc0: p ^= 0x80; break; } } return ((isprint(p) ? p : '.')); } /* Converts a PETSCII character to ASCII if possible, returns negative PETSCII code otherwise */ static int p2a_escape(unsigned char p) { if((p >= 32 && p <= 122) && (p != 33) && (p != 34) && (p != 35) && (p != 36) && (p != 92) && (p != 94) && (p != 95) && (p != 96)) { switch (p & 0xe0) { case 0x40: case 0x60: return p ^ 0x20; break; default: return p; } } else { return -p; } } /* Converts a PETSCII string to ASCII with hex escapes where needed, esacpe slash if slash false */ static char* petscii2ascii(const unsigned char* petscii, char* ascii, int len, bool slash) { /* find end of petscii string */ while(len > 1 && petscii[len-1] == 0xa0) { len--; } int apos = 0; for(int ppos = 0; ppos < len; ppos++) { int a = p2a_escape(petscii[ppos]); if(!slash && (a == 47)) { a = -a; /* force slash to be escaped */ } if(a < 0) { snprintf(ascii+apos, 4, "#%02x", -a); apos += 3; } else { ascii[apos++] = a; } } ascii[apos] = 0; return ascii; } /* Converts an ASCII string to PETSCII filled up with Shift-Space to length */ static void ascii2petscii(const unsigned char* ascii, unsigned char* petscii, int len) { int pos = 0; while (ascii[pos] != '\0' && pos < len) { petscii[pos] = a2p(ascii[pos]); ++pos; } while (pos < len) { petscii[pos] = FILENAMEEMPTYCHAR; ++pos; } } /* Prints a PETSCII filename as ASCII with escapes */ static void print_filename_with_escapes(const unsigned char* petscii, int len) { char ascii[3 * FILENAMEMAXSIZE + 1]; fputs(petscii2ascii(petscii, ascii, len, true), stdout); } /* Determines length of 0xa0 terminated PETSCII string */ static int pstrlen(const unsigned char* string) { int len = 0; while(string[len] != 0xa0 && len < FILENAMEMAXSIZE) { len++; } return len; } /* Writes decimal number into PETSCII string */ #define PPUTNUM_BUF_LEN 17 static void pputnum(unsigned char* string, unsigned int num) { char buffer[PPUTNUM_BUF_LEN]; snprintf(buffer, PPUTNUM_BUF_LEN, "%d", num); for(unsigned int len = 0; len < strlen(buffer); len++) { string[len] = a2p(buffer[len]); } } /* Writes 2 digit decimal number into PETSCII string */ #define PPUTNUM2_BUF_LEN 3 static void pputnum2(unsigned char* string, unsigned int num) { char buffer[PPUTNUM2_BUF_LEN]; snprintf(buffer, PPUTNUM2_BUF_LEN, "%02d", num); for(unsigned int len = 0; len < strlen(buffer); len++) { string[len] = a2p(buffer[len]); } } /* Writes 4 digit hex number into PETSCII string */ #define PPUTHEX_BUF_LEN 5 static void pputhex(unsigned char* string, unsigned int num) { char buffer[PPUTHEX_BUF_LEN]; snprintf(buffer, PPUTHEX_BUF_LEN, "%04x", num); for(int len = 0; len < 4; len++) { string[len] = a2p(buffer[len]); } } /* Converts a two digit hex string to an int */ static unsigned int hex2int(char hex) { if ((hex < '0' || hex > '9') && (hex < 'a' || hex > 'f') && (hex < 'A' || hex > 'F')) { fprintf(stderr, "ERROR: Invalid hex string in filename\n"); exit(-1); } if (hex <= '9') { hex -= '0'; } else if(hex >= 'A' && hex <= 'F') { hex -= 'A' - 10; } else if(hex >= 'a' && hex <= 'f') { hex -= 'a' - 10; } return (unsigned int)hex; } /* Converts an ASCII string to PETSCII with escape evaluation, filled up with emptychar to length */ static void evalhexescape(const unsigned char* ascii, unsigned char* petscii, int len, unsigned char emptychar) { int read = 0, write = 0; while (ascii[read] != '\0' && write < len) { if (ascii[read] == '#') { unsigned int hi = hex2int(ascii[++read]); unsigned int lo = hex2int(ascii[++read]); petscii[write] = (unsigned char)(16 * hi + lo); } else { petscii[write] = a2p(ascii[read]); } read++; write++; } while (write < len) { petscii[write] = emptychar; ++write; } } /* Converts a unicode character to utf8 */ char* utf8_encode(int c, char* out) { if (c < 0x80) *out++ =(c & 0xff); else if (c < 0x800) { *out++ = (0xC0 | ((c >> 6) & 0x1f)); *out++ = (0x80 | (c & 0x3f)); } else if (c < 0x10000) { *out++ = (0xE0 | ((c >> 12) & 0xf)); *out++ = (0x80 | ((c >> 6) & 0x3f)); *out++ = (0x80 | (c & 0x3f)); } else { *out++ = (0xF0 | ((c >> 18) & 0x07)); *out++ = (0x80 | ((c >> 12) & 0x3f)); *out++ = (0x80 | ((c >> 6) & 0x3f)); *out++ = (0x80 | (c & 0x3f)); } return out; } #ifdef _WIN32 /* Enables console formatting under Windows if possible */ static bool EnableVTMode() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE) { return false; } DWORD dwMode = 0; if (!GetConsoleMode(hOut, &dwMode)) { return false; } dwMode |= 0x0004; /* ENABLE_VIRTUAL_TERMINAL_PROCESSING not defined for older SDKs */ if (!SetConsoleMode(hOut, dwMode)) { return false; } return true; } #endif /* Enables reverse printing to console */ static void reverse_print_on(void) { #ifdef _WIN32 /* Avoid escape values for inverse printing under Windows if they are not supported by the console */ if (EnableVTMode()) { printf("\033[7m"); } #else printf("\033[7m"); #endif } /* Disables reverse printing to console */ static void reverse_print_off(void) { #ifdef _WIN32 /* Avoid escape values for inverse printing under Windows if they are not supported by the console */ if (EnableVTMode()) { printf("\033[m"); } #else printf("\033[m"); #endif } /* Prints a PETSCII character */ static void putp(unsigned char petscii, FILE *file) { if (unicode) { int u; int reverse = 0; if (petscii < 0x20 || (petscii >= 0x80 && petscii <= 0x9f)) { reverse = 1; reverse_print_on(); petscii += 0x40; } if (unicode == 1) { u = p2u_uppercase_tab[petscii]; } else { u = p2u_lowercase_tab[petscii]; } #ifdef _WIN32 _setmode(_fileno(file), _O_U16TEXT); putwc(u, file); _setmode(_fileno(file), _O_TEXT); #else { char temp[5]; *utf8_encode(u, temp) = 0; /* fancy way to zero-terminate temp after conversion */ fputs(temp, file); } #endif if(reverse) { reverse_print_off(); } } else { putc(p2a(petscii), file); } } /* Prints a PETSCII string */ static void print_petscii(unsigned char *petscii, int len) { for(int i = 0; i < len; i++) { putp(petscii[i], stdout); } } /* Prints the given PETSCII filename like the C64 when listing the directory */ static void print_dirfilename(unsigned char* pfilename) { int ended = 0; putc('\"', stdout); for (int pos = 0; pos < FILENAMEMAXSIZE; pos++) { unsigned char c = pfilename[pos]; if (c == FILENAMEEMPTYCHAR) { if (!ended) { putc('\"', stdout); ended = 1; } else { putc(' ', stdout); } } else { putp(c, stdout); } } if (!ended) { putc('\"', stdout); } else { putc(' ', stdout); } } /* Prints the given PETSCII filename */ static void print_filename(FILE *file, unsigned char* pfilename) { putc('\"', file); for (int pos = 0; pos < FILENAMEMAXSIZE; pos++) { if (pfilename[pos] == FILENAMEEMPTYCHAR) { break; } putp(pfilename[pos], file); } putc('\"', file); } /* Calculates the overall sector index from a given track and sector, returns -1 for invalid track/sector */ static int linear_sector(image_type type, int track, int sector) { if ((track < 1) || (track > ((type == IMAGE_D81) ? D81NUMTRACKS : ((type == IMAGE_D64) ? D64NUMTRACKS : (type == IMAGE_D71 ? D71NUMTRACKS : D64NUMTRACKS_EXTENDED))))) { return -1; } int numsectors = num_sectors(type, track); if ((sector < 0) || (sector >= numsectors)) { return -1; } int linear_sector = 0; for (int i = 0; i < track - 1; i++) { linear_sector += num_sectors(type, i + 1); } linear_sector += sector; return linear_sector; } /* Returns the image offset of the bam entry for a given track */ static int get_bam_offset(image_type type, unsigned int track) { int bam; unsigned int offset; if (type == IMAGE_D81) { if (track <= 40) { bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE; offset = bam + (track * 6) + 11; } else { bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE; offset = bam + ((track - 40) * 6) + 11; } } else if ((type == IMAGE_D71) && (track > D64NUMTRACKS)) { /* access second side bam */ bam = linear_sector(type, dirtrack(type) + D64NUMTRACKS, 0) * BLOCKSIZE; offset = bam + (track - D64NUMTRACKS - 1) * 3; } else { if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (track > D64NUMTRACKS)) { track -= D64NUMTRACKS; bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + ((type == IMAGE_D64_EXTENDED_SPEED_DOS) ? BAM_OFFSET_SPEED_DOS : BAM_OFFSET_DOLPHIN_DOS); } else { bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; } offset = bam + track * 4 + 1; } return offset; } /* Checks if a given sector is marked as free in the BAM and also not used by directory */ static int is_sector_free(image_type type, const unsigned char* image, int track, int sector, int numdirblocks, int dir_sector_interleave) { int bam; const unsigned char* bitmap; if (sector < 0) { fprintf(stderr, "ERROR: Illegal sector %d for track %d\n", sector, track); exit(-1); } if (type == IMAGE_D81) { if (track <= 40) { bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE; bitmap = image + bam + (track * 6) + 11; } else { bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE; bitmap = image + bam + ((track - 40) * 6) + 11; } } else if ((type == IMAGE_D71) && (track > D64NUMTRACKS)) { /* access second side bam */ bam = linear_sector(type, dirtrack(type) + D64NUMTRACKS, 0) * BLOCKSIZE; bitmap = image + bam + (track - D64NUMTRACKS - 1) * 3; } else { if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (track > D64NUMTRACKS)) { track -= D64NUMTRACKS+1; bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + ((type == IMAGE_D64_EXTENDED_SPEED_DOS) ? BAM_OFFSET_SPEED_DOS : BAM_OFFSET_DOLPHIN_DOS); } else { bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; } bitmap = image + bam + (track * 4) + 1; } int byte = sector >> 3; int bit = sector & 7; int is_not_dir_block = 1; if ((track == dirtrack(type)) && (numdirblocks > 0)) { int dirsector = 0; int s = 2; for (int i = 0; is_not_dir_block && (i < numdirblocks); i++) { switch (i) { case 0: dirsector = 0; break; case 1: dirsector = 1; break; default: dirsector += dir_sector_interleave; if (dirsector >= num_sectors(type, track)) { dirsector = s; s++; } break; } is_not_dir_block = (sector != dirsector); } } return is_not_dir_block && ((bitmap[byte] & (1 << bit)) != 0); } /* Marks given sector in BAM */ static void mark_sector(image_type type, unsigned char* image, int track, int sector, int free) { if (free != is_sector_free(type, image, track, sector, 0, 0)) { int bam; unsigned char* bitmap; if (type == IMAGE_D81) { if (track <= 40) { bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE; bitmap = image + bam + (track * 6) + 11; } else { bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE; bitmap = image + bam + ((track - 40) * 6) + 11; } /* update number of free sectors on track */ if (free) { ++bitmap[-1]; } else { --bitmap[-1]; } } else if ((type == IMAGE_D71) && (track > D64NUMTRACKS)) { /* update number of free sectors on track */ bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; if (free) { image[bam + 0xdd + track - D64NUMTRACKS - 1]++; } else { image[bam + 0xdd + track - D64NUMTRACKS - 1]--; } /* access bitmap on second side bam */ bam = linear_sector(type, dirtrack(type) + D64NUMTRACKS, 0) * BLOCKSIZE; bitmap = image + bam + (track - D64NUMTRACKS - 1) * 3; } else { if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (track > D64NUMTRACKS)) { track -= D64NUMTRACKS+1; bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + ((type == IMAGE_D64_EXTENDED_SPEED_DOS) ? BAM_OFFSET_SPEED_DOS : BAM_OFFSET_DOLPHIN_DOS); } else { bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; } bitmap = image + bam + (track * 4) + 1; /* update number of free sectors on track */ if (free) { ++image[bam + (track * 4)]; } else { --image[bam + (track * 4)]; } } /* update bitmap */ int byte = sector >> 3; int bit = sector & 7; if (free) { bitmap[byte] |= 1 << bit; } else { bitmap[byte] &= ~(1 << bit); } } } /* Returns offset for header on directory track */ static int get_header_offset(image_type type) { int offset; if (type == IMAGE_D81) { offset = 4; } else { offset = 0x90; } return offset; } /* Returns offset for id on directory track */ static int get_id_offset(image_type type) { int offset; if (type == IMAGE_D81) { offset = 0x16; } else { offset = 0xa2; } return offset; } /* Updates the directory with the given header, id and BAM message */ static void update_directory(image_type type, unsigned char* image, unsigned char* header, unsigned char* id, unsigned char *bam_message, int shadowdirtrack) { unsigned int bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; if (type != IMAGE_D81) { image[bam + 0x03] = (type == IMAGE_D71) ? 0x80 : 0x00; } /* Set header and ID */ unsigned char pheader[FILENAMEMAXSIZE]; unsigned char pid[5]; evalhexescape(header, pheader, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR); evalhexescape(id, pid, 5, FILENAMEEMPTYCHAR); memcpy(image + bam + get_header_offset(type), pheader, FILENAMEMAXSIZE); memcpy(image + bam + get_id_offset(type), pid, 5); /* Set BAM message */ if(bam_message != NULL) { unsigned char pbam_message[BAMMESSAGEMAXSIZE]; int bam_message_len = BAMMESSAGEMAXSIZE; if(type == IMAGE_D64_EXTENDED_SPEED_DOS) { bam_message_len = 0xbf - BAMMESSAGEOFFSET; /* avoid conflict with extended BAM and allow for a 0 at the end*/ } evalhexescape(bam_message, pbam_message, bam_message_len, 0); memcpy(image + bam + BAMMESSAGEOFFSET, pbam_message, bam_message_len); } if (type == IMAGE_D81) { unsigned int bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE; image[bam + 0x04] = id[0]; image[bam + 0x05] = id[1]; bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE; image[bam + 0x04] = id[0]; image[bam + 0x05] = id[1]; } if (shadowdirtrack > 0) { unsigned int shadowbam = linear_sector(type, shadowdirtrack, 0 /* sector */) * BLOCKSIZE; memcpy(image + shadowbam, image + bam, BLOCKSIZE); image[shadowbam + 0x00] = shadowdirtrack; } } /* Writes an empty directory and BAM */ static void initialize_directory(image_type type, unsigned char* image, unsigned char* header, unsigned char* id, unsigned char * bam_message, int shadowdirtrack, int usedirtrack) { unsigned int dir = linear_sector(type, dirtrack(type), 0 /* sector */) * BLOCKSIZE; /* Clear image */ memset(image, 0, image_size(type)); /* Write initial BAM */ if (type == IMAGE_D81) { image[dir + 0x00] = dirtrack(type); image[dir + 0x01] = 3; image[dir + 0x02] = 0x44; image[dir + 0x14] = FILENAMEEMPTYCHAR; image[dir + 0x15] = FILENAMEEMPTYCHAR; image[dir + 0x1b] = FILENAMEEMPTYCHAR; image[dir + 0x1c] = FILENAMEEMPTYCHAR; unsigned int bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE; image[bam + 0x00] = dirtrack(type); image[bam + 0x01] = 2; image[bam + 0x02] = 0x44; image[bam + 0x03] = 0xbb; image[bam + 0x06] = 0xc0; bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE; image[bam + 0x00] = 0; image[bam + 0x01] = 255; image[bam + 0x02] = 0x44; image[bam + 0x03] = 0xbb; image[bam + 0x06] = 0xc0; } else { image[dir + 0x00] = dirtrack(type); image[dir + 0x01] = 1; image[dir + 0x02] = 0x41; image[dir + 0x03] = (type == IMAGE_D71) ? 0x80 : 0x00; image[dir + 0xa0] = FILENAMEEMPTYCHAR; image[dir + 0xa1] = FILENAMEEMPTYCHAR; image[dir + 0xa7] = FILENAMEEMPTYCHAR; image[dir + 0xa8] = FILENAMEEMPTYCHAR; image[dir + 0xa9] = FILENAMEEMPTYCHAR; image[dir + 0xaa] = FILENAMEEMPTYCHAR; } /* Mark all sectors unused */ for (unsigned int t = 1; t <= image_num_tracks(type); t++) { for (int s = 0; s < num_sectors(type, t); s++) { mark_sector(type, image, t, s, 1 /* free */); } } /* Reserve space for BAM */ mark_sector(type, image, dirtrack(type), 0 /* sector */, 0 /* not free */); if (type == IMAGE_D71) { mark_sector(type, image, dirtrack(type) + D64NUMTRACKS, 0, 0 /* not free */); if(!usedirtrack) { for (int s = 1; s < num_sectors(type, dirtrack(type) + D64NUMTRACKS); s++) { mark_sector(type, image, dirtrack(type) + D64NUMTRACKS, s, 0 /* not free */); } } } else if (type == IMAGE_D81) { mark_sector(type, image, dirtrack(type), 1 /* sector */, 0 /* not free */); mark_sector(type, image, dirtrack(type), 2 /* sector */, 0 /* not free */); } /* first dir block */ unsigned int dirblock = linear_sector(type, dirtrack(type), (type == IMAGE_D81) ? 3 : 1) * BLOCKSIZE; image[dirblock + SECTORLINKOFFSET] = 255; mark_sector(type, image, dirtrack(type), (type == IMAGE_D81) ? 3 : 1 /* sector */, 0 /* not free */); if (shadowdirtrack > 0) { dirblock = linear_sector(type, shadowdirtrack, (type == IMAGE_D81) ? 3 : 1 /* sector */) * BLOCKSIZE; image[dirblock + SECTORLINKOFFSET] = 255; mark_sector(type, image, shadowdirtrack, 0 /* sector */, 0 /* not free */); mark_sector(type, image, shadowdirtrack, (type == IMAGE_D81) ? 3 : 1 /* sector */, 0 /* not free */); } update_directory(type, image, header, id, bam_message, shadowdirtrack); } /* Computes Transwarp dirdata checksum */ static unsigned char transwarp_dirdata_checksum(const unsigned char *image, int dir_entry_offset) { int dirdata_checksum = 0; int carry = 1; for (int offset = DIRDATACHECKSUMOFFSET; offset <= FILEBLOCKSLOOFFSET; ++offset) { dirdata_checksum += (image[dir_entry_offset + offset] + carry); carry = (dirdata_checksum >= 0x0100) ? 1 : 0; dirdata_checksum &= 0xff; } return dirdata_checksum; } /* Checks if a given dir entry points to a Transwarp file */ static bool is_transwarp_file(const unsigned char *image, int dir_entry_offset) { return (image[dir_entry_offset + TRANSWARPSIGNATROFFSLO] == TRANSWARPSIGNATURELO) && (image[dir_entry_offset + TRANSWARPSIGNATROFFSHI] == TRANSWARPSIGNATUREHI) && (transwarp_dirdata_checksum(image, dir_entry_offset) == 0); } /* Checks if a given dir entry points to the Transwarp bootfile */ static bool is_transwarp_bootfile(const unsigned char *image, int dir_entry_offset) { return memcmp(image + dir_entry_offset + FILENAMEOFFSET, TRANSWARP, strlen(TRANSWARP)) == 0; } /* Return Transwarp file stat */ static int transwarp_stat(image_type type, const unsigned char *image, int dir_entry_offset, int *start_track, int *end_track, int *low_track, int *high_track) { *start_track = 0; *end_track = 0; *low_track = 0; *high_track = 0; int filesize = (image[dir_entry_offset + ENDADDRESSLOOFFSET] | (image[dir_entry_offset + ENDADDRESSHIOFFSET] << 8)) - (image[dir_entry_offset + LOADADDRESSLOOFFSET] | (image[dir_entry_offset + LOADADDRESSHIOFFSET] << 8)); if (filesize <= 0) { return 0; } *start_track = image[dir_entry_offset + TRANSWARPTRACKOFFSET]; *end_track = *start_track; int size = filesize; while (filesize > 0) { filesize -= (TRANSWARPBLOCKSIZE * num_sectors(type, *end_track)); if (filesize > 0) { *end_track = (*end_track < DIRTRACK_D41_D71) ? (*end_track - 1) : (*end_track + 1); } } *low_track = (*start_track < *end_track) ? *start_track : *end_track; *high_track = (*start_track > *end_track) ? *start_track : *end_track; return size; } /* Return Transwarp file stat */ static int transwarp_size(image_type type, int start_track, int end_track, int filesize, int *transwarp_blocks, int *nonredundant_blocks, int *redundant_blocks, int *nonredundant_blocks_on_last_track) { *transwarp_blocks = 0; int last_track_sectors; if (start_track <= end_track) { for (int track = start_track; track <= end_track; ++track) { last_track_sectors = num_sectors(type, track); *transwarp_blocks += last_track_sectors; } } else { for (int track = start_track; track >= end_track; --track) { last_track_sectors = num_sectors(type, track); *transwarp_blocks += last_track_sectors; } } int spare_bytes = TRANSWARPBLOCKSIZE - (filesize % TRANSWARPBLOCKSIZE); if (spare_bytes == TRANSWARPBLOCKSIZE) { spare_bytes = 0; } *nonredundant_blocks = (filesize / TRANSWARPBLOCKSIZE) + ((spare_bytes == 0) ? 0 : 1); *redundant_blocks = *transwarp_blocks - *nonredundant_blocks; *nonredundant_blocks_on_last_track = last_track_sectors - *redundant_blocks; return spare_bytes; } /* Deletes a file from disk and BAM, but leaves the directory entry */ static void wipe_file(image_type type, unsigned char* image, imagefile* file) { int b = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset; if (is_transwarp_file(image, b)) { int start_track; int end_track; int low_track; int high_track; int filesize = transwarp_stat(type, image, b, &start_track, &end_track, &low_track, &high_track); if (filesize <= 0) { fprintf(stderr, "ERROR: Cannot overwrite Transwarp file "); print_filename(stderr, file->pfilename); fprintf(stderr, "\n"); exit(-1); } for (int track = low_track; track <= high_track; ++track) { for (int sector = 0; sector < num_sectors(type, track); ++sector) { int block_offset = linear_sector(type, track, sector) * BLOCKSIZE; memset(image + block_offset, 0, BLOCKSIZE); mark_sector(type, image, track, sector, 1 /* free */); } } return; } unsigned int track = image[b + FILETRACKOFFSET]; unsigned int sector = image[b + FILESECTOROFFSET]; if (sector >= 0x80) { return; /* loop file */ } while (track != 0) { int block_offset = linear_sector(type, track, sector) * BLOCKSIZE; int next_track = image[block_offset + TRACKLINKOFFSET]; int next_sector = image[block_offset + SECTORLINKOFFSET]; memset(image + block_offset, 0, BLOCKSIZE); /* this also fixes any cyclic t/s chain */ mark_sector(type, image, track, sector, 1 /* free */); track = next_track; sector = next_sector; } } /* Sets image offset to the next DIR entry, returns false when the DIR end was reached */ static bool next_dir_entry(image_type type, const unsigned char* image, int *track, int *sector, int *offset, char *blockmap) { int b = linear_sector(type, *track, *sector); /* assuming here that the given t/s is valid */ blockmap[b] = 1; if (*offset % BLOCKSIZE == 7 * DIRENTRYSIZE) { /* last entry in sector */ int next_track = image[b * BLOCKSIZE + TRACKLINKOFFSET]; if (next_track == 0) { /* this was the last DIR sector */ return false; } int next_sector = image[b * BLOCKSIZE + SECTORLINKOFFSET]; b = linear_sector(type, next_track, next_sector); if(b < 0) { dir_error = DIR_ILLEGAL_TS; return false; } if(blockmap[b]) { dir_error = DIR_CYCLIC_TS; return false; } *track = next_track; *sector = next_sector; *offset = 0; } else { *offset += DIRENTRYSIZE; } return true; } /* Finds all filenames with the given hash */ static int count_hashes(image_type type, const unsigned char* image, unsigned int hash, bool print) { int num = 0; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET]; if (filetype != FILETYPEDEL) { unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET; if (hash == filenamehash(filename)) { ++num; if (print) { printf(" [$%04x] ", filenamehash(filename)); print_filename(stdout, filename); printf("\n"); } } } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); return num; } /* Checks if multiple filenames have the same hash */ static bool check_hashes(image_type type, const unsigned char* image) { bool collision = false; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } printf("\n"); int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET]; if (filetype != FILETYPEDEL) { unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET; if (count_hashes(type, image, filenamehash(filename), false /* print */) > 1) { collision = 1; fprintf(stderr, "Hash of filename "); print_filename(stderr, filename); fprintf(stderr, " [$%04x] is not unique\n", filenamehash(filename)); count_hashes(type, image, filenamehash(filename), true /* print */); } } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); return collision; } /* Compare filenames with wildcards */ static bool match_filename(const unsigned char *pattern, const unsigned char *filename) { for(int c = 0; c < FILENAMEMAXSIZE; c++) { if(pattern[c] == 42) { /* PETSCII * */ return true; } if((pattern[c] != 63) && (pattern[c] != filename[c])) { /* PETSCII ? or same character */ return false; } } return true; } /* Searches for an existing DIR entry with the given name, returns false if it does not exist */ static bool find_existing_file(image_type type, unsigned char* image, unsigned char* filename, int *index, int *track, int *sector, int *offset) { *track = dirtrack(type); *sector = (type == IMAGE_D81) ? 3 : 1; *offset = 0; *index = 0; bool exists = false; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } do { int b = linear_sector(type, *track, *sector) * BLOCKSIZE + *offset; int filetype = image[b + FILETYPEOFFSET]; if(filetype != 0 && match_filename(filename, image + b + FILENAMEOFFSET)) { exists = true; break; } ++(*index); } while (next_dir_entry(type, image, track, sector, offset, blockmap)); free(blockmap); return exists; } /* Returns an empty DIR slot, allocates a new DIR sector if required */ static void new_dir_slot(image_type type, unsigned char* image, int dir_sector_interleave, int shadowdirtrack, int *index, int *dirsector, int *entry_offset, imagefile files[]) { int track = dirtrack(type); *dirsector = (type == IMAGE_D81) ? 3 : 1; *entry_offset = 0; int lindex = 0; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } do { int b = linear_sector(type, track, *dirsector) * BLOCKSIZE + *entry_offset; if (image[b + FILETYPEOFFSET] == FILETYPEDEL) { /* Check if the apparently free slot is used by a new filetype 0 file */ bool used = false; for(int f = 0; f < num_files; f++) { if(files[f].direntryindex == lindex) { used = true; break; } } if(!used) { *index = lindex; free(blockmap); return; /* found an empty slot */ } } ++lindex; } while (next_dir_entry(type, image, &track, dirsector, entry_offset, blockmap)); free(blockmap); /* allocate new dir block */ int last_sector = *dirsector; int next_sector = -1; for (int s = 1; s < num_sectors(type, dirtrack(type)); s++) { int sector = (last_sector + s * dir_sector_interleave) % num_sectors(type, dirtrack(type)); if (is_sector_free(type, image, dirtrack(type), sector, 0, 0)) { next_sector = sector; break; } } if (next_sector == -1) { fprintf(stderr, "ERROR: Dir track full\n"); exit(-1); } int b = linear_sector(type, dirtrack(type), last_sector) * BLOCKSIZE; image[b + TRACKLINKOFFSET] = dirtrack(type); image[b + SECTORLINKOFFSET] = next_sector; mark_sector(type, image, dirtrack(type), next_sector, 0 /* not free */); b = linear_sector(type, dirtrack(type), next_sector) * BLOCKSIZE; memset(image + b, 0, BLOCKSIZE); image[b + SECTORLINKOFFSET] = 255; *dirsector = next_sector; *entry_offset = 0; if (shadowdirtrack > 0) { b = linear_sector(type, shadowdirtrack, last_sector) * BLOCKSIZE; image[b + TRACKLINKOFFSET] = shadowdirtrack; image[b + SECTORLINKOFFSET] = next_sector; mark_sector(type, image, shadowdirtrack, next_sector, 0 /* not free */); b = linear_sector(type, shadowdirtrack, next_sector) * BLOCKSIZE; memset(image + b, 0, BLOCKSIZE); image[b + SECTORLINKOFFSET] = 255; } *index = lindex; } /* Returns suitable index and offset for given filename (either existing slot when overwriting, first free slot or slot in newly allocated segment) */ static bool find_dir_slot(image_type type, unsigned char* image, unsigned char* filename, int dir_sector_interleave, int shadowdirtrack, int *index, int *dirsector, int *entry_offset, imagefile files[]) { int track; if(find_existing_file(type, image, filename, index, &track, dirsector, entry_offset)) { return true; } new_dir_slot(type, image, dir_sector_interleave, shadowdirtrack, index, dirsector, entry_offset, files); return false; } /* Adds the specified new entries to the directory */ static void create_dir_entries(image_type type, unsigned char* image, imagefile* files, int num_files, int dir_sector_interleave, unsigned int shadowdirtrack, int nooverwrite) { /* this does not check for uniqueness of filenames */ int num_overwritten_files = 0; if (verbose && num_files > 0) { printf("\nCreating dir entries:\n"); } for (int i = 0; i < num_files; i++) { /* find or create slot */ imagefile *file = files + i; if (verbose) { printf(" "); print_dirfilename(file->pfilename); } if(file->force_new) { new_dir_slot(type, image, dir_sector_interleave, shadowdirtrack, &file->direntryindex, &file->direntrysector, &file->direntryoffset, files); } else if (find_dir_slot(type, image, file->pfilename, dir_sector_interleave, shadowdirtrack, &file->direntryindex, &file->direntrysector, &file->direntryoffset, files)) { if (nooverwrite) { fprintf(stderr, "ERROR: Filename exists on disk image already and -o was set\n"); exit(-1); } wipe_file(type, image, file); num_overwritten_files++; } int file_entry_offset = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[file_entry_offset + FILETYPEOFFSET] = file->filetype & 0xff; if (verbose && (file->filetype & FILETYPETRANSWARPMASK)) { printf(" [Transwarp]"); } memcpy(image + file_entry_offset + FILENAMEOFFSET, file->pfilename, FILENAMEMAXSIZE); if (is_transwarp_bootfile(image, file_entry_offset)) { if (file->filetype & FILETYPETRANSWARPMASK) { if (verbose) { printf("\n"); } fprintf(stderr, "ERROR: Attempt to write Transwarp bootfile as Transwarp file\n"); exit(-1); } file->mode |= MODE_TRANSWARPBOOTFILE; if (verbose) { printf(" [Transwarp bootfile]"); } if (i != 0) { // allocate Transwarp bootfile first if ((files[0].mode & MODE_TRANSWARPBOOTFILE) != 0) { if (verbose) { printf("\n"); } fprintf(stderr, "ERROR: Multiple Transwarp bootfiles\n"); exit(-1); } imagefile transwarp_bootfile = *file; for (int j = i; j > 0; --j) { files[j] = files[j - 1]; } files[0] = transwarp_bootfile; file = files; } } if (shadowdirtrack > 0) { file_entry_offset = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[file_entry_offset + FILETYPEOFFSET] = file->filetype; memcpy(image + file_entry_offset + FILENAMEOFFSET, file->pfilename, FILENAMEMAXSIZE); } if (verbose) { printf("\n"); } } if (!quiet && (num_overwritten_files > 0)) { printf("%d out of %d files exist and will be overwritten\n", num_overwritten_files, num_files); } } /* Prints the allocated tracks and sectors for every file */ static void print_file_allocation(image_type type, const unsigned char* image, imagefile* files, int num_files) { if (num_files <= 0) { imagefile existing_files[144]; memset(existing_files, 0, sizeof existing_files); int t = dirtrack(type); int s = (type == IMAGE_D81) ? 3 : 1; int o = 0; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } do { int b = linear_sector(type, t, s) * BLOCKSIZE + o; int filetype = image[b + FILETYPEOFFSET] & 0xf; switch (filetype) { case FILETYPEPRG: { int track = image[b + FILETRACKOFFSET]; int sector = image[b + FILESECTOROFFSET]; int numsectors = image[b + FILEBLOCKSLOOFFSET] + 256 * image[b + FILEBLOCKSHIOFFSET]; const unsigned char *filename = image + b + FILENAMEOFFSET; b = linear_sector(type, track, sector); if (b >= 0) { memcpy(existing_files[num_files].pfilename, filename, FILENAMEMAXSIZE); existing_files[num_files].track = track; existing_files[num_files].sector = sector; existing_files[num_files].direntryindex = num_files; existing_files[num_files].direntrysector = s; existing_files[num_files].direntryoffset = o; existing_files[num_files].nrSectors = numsectors; if (is_transwarp_file(image, b)) { existing_files[num_files].filetype = filetype | FILETYPETRANSWARPMASK; existing_files[num_files].sectorInterleave = 1; int start_track; int end_track; int low_track; int high_track; int filesize = transwarp_stat(type, image, b, &start_track, &end_track, &low_track, &high_track); existing_files[num_files].size = filesize; existing_files[num_files].track = start_track; existing_files[num_files].last_track = end_track; ++num_files; break; } while (true) { b = linear_sector(type, track, sector); if(b < 0) { break; /* TODO: print info about illegal t/s */ } int offset = b * BLOCKSIZE; int next_track = image[offset + 0]; int next_sector = image[offset + 1]; if ((track == 0) || (next_track == 0)) { break; } if ((track == next_track) && (next_sector > sector)) { existing_files[num_files].sectorInterleave = next_sector - sector; break; } track = next_track; sector = next_sector; } ++num_files; break; } } default: break; } } while (next_dir_entry(type, image, &t, &s, &o, blockmap)); free(blockmap); files = existing_files; } if(num_files > 0) { printf("\nFile allocation:\n"); } for (int i = 0; i < num_files; i++) { printf("%02d/%02d %3d (0x%02x 0x%02x:0x%02x) ", files[i].track, files[i].sector, files[i].nrSectors & 0xffff, files[i].direntryindex, files[i].direntrysector, files[i].direntryoffset); if (files[i].alocalname) { printf("\"%s\" => ", files[i].alocalname); } print_filename(stdout, files[i].pfilename); printf(" (SL: %d)", files[i].sectorInterleave); if ((files[i].mode & MODE_LOOPFILE) && (files[i].sectorInterleave != 0)) { continue; } if (files[i].filetype & FILETYPETRANSWARPMASK) { int transwarp_blocks; int nonredundant_blocks; int redundant_blocks; int nonredundant_blocks_on_last_track; int spare_bytes = transwarp_size(type, files[i].track, files[i].last_track, files[i].size, &transwarp_blocks, &nonredundant_blocks, &redundant_blocks, &nonredundant_blocks_on_last_track); int num_blocks = 0; int filesize = files[i].size + 2; while (filesize > 0) { ++num_blocks; filesize -= 254; } printf("\n Transwarp: %d total/%d actual (%d standard) blocks, tracks %d-%d, %d used and %d redundant block%s on last track, 0x%x spare bytes in last block, size 0x%x\n", transwarp_blocks, nonredundant_blocks, num_blocks, (files[i].track < files[i].last_track) ? files[i].track : files[i].last_track, (files[i].track >= files[i].last_track) ? files[i].track : files[i].last_track, nonredundant_blocks_on_last_track, redundant_blocks, (redundant_blocks == 1) ? "" : "s", spare_bytes, files[i].size); continue; } int track = files[i].track; int sector = files[i].sector; bool firsttrack = true; int firstsector = sector; bool fileblocks[SECTORSPERTRACK_D81]; memset(fileblocks, 0, sizeof fileblocks); fileblocks[sector] = true; int j = 0; while (track != 0) { if (j == 0) { printf("\n "); } printf("%02d/%02d", track, sector); int b = linear_sector(type, track, sector); if(b < 0) { break; /* TODO: print info about illegal t/s link? */ } int offset = b * BLOCKSIZE; int next_track = image[offset + 0]; int next_sector = image[offset + 1]; if ((track != next_track) && (next_track != 0)) { /* track change */ if (next_sector != 0) { /* interleave violation */ printf("!-"); } else { printf(" -"); } } else if ((next_sector < sector) && (next_track != 0)) { /* sector wrap */ int expected_next_sector = ((sector + abs(files[i].sectorInterleave)) % num_sectors(type, track)); bool on_nonempty_firsttrack = (expected_next_sector < next_sector) && firsttrack && (firstsector != 0); if ((expected_next_sector != next_sector) && (!on_nonempty_firsttrack)) { while ((expected_next_sector < next_sector) && fileblocks[expected_next_sector]) { ++expected_next_sector; } if (expected_next_sector != next_sector) { /* interleave violation */ printf("!."); } else { printf(" ."); } } else { printf(" ."); } } else if (((next_sector - sector) != abs(files[i].sectorInterleave)) && (next_track != 0)) { /* interleave violation */ printf(" !"); } else { printf(" "); } if (track != next_track) { memset(fileblocks, 0, sizeof fileblocks); firsttrack = false; } track = next_track; sector = next_sector; if (next_track != 0) { fileblocks[sector] = true; } j++; if (j == 10) { j = 0; } } printf("\n"); } printf("\n"); } static void assign_blocktags(image_type type, const unsigned char *image, int(*blocktags)[SECTORSPERTRACK_D81]) { char c = '@'; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET] & 0xf; if (filetype != 0) { int filetrack = image[dirblock + FILETRACKOFFSET]; int filesector = image[dirblock + FILESECTOROFFSET]; if (is_transwarp_file(image, dirblock)) { int start_track; int end_track; int low_track; int high_track; int filesize = transwarp_stat(type, image, dirblock, &start_track, &end_track, &low_track, &high_track); if (filesize <= 0) { continue; } int transwarp_blocks; int nonredundant_blocks; int redundant_blocks; int nonredundant_blocks_on_last_track; transwarp_size(type, start_track, end_track, filesize, &transwarp_blocks, &nonredundant_blocks, &redundant_blocks, &nonredundant_blocks_on_last_track); for (int track = low_track; track <= high_track; ++track) { for (int sector = 0; sector < SECTORSPERTRACK_D81; ++sector) { blocktags[track][sector] = c + (((track == end_track) && (sector >= nonredundant_blocks_on_last_track)) ? 256 : 0); } } } else { bool new_track = true; while (filetrack != 0) { int b = linear_sector(type, filetrack, filesector); if(b < 0) { break; } int block_offset = b * BLOCKSIZE; int next_track = image[block_offset + TRACKLINKOFFSET]; int next_sector = image[block_offset + SECTORLINKOFFSET]; blocktags[filetrack][filesector] = c + (new_track ? 256 : 0); new_track = (filetrack != next_track); filetrack = next_track; filesector = next_sector; } } switch (c) { default: ++c; break; case 'Z': c = '0'; break; case '9': c = 'a'; break; case 'z': c = '@'; break; } } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); } /* Returns true if the file starting on the given filetrack and filesector uses the given track */ static bool fileontrack(image_type type, const unsigned char *image, int track, int filetrack, int filesector) { while (filetrack != 0) { if (filetrack == track) { return true; } int b = linear_sector(type, filetrack, filesector); if(b < 0) { return false; } int block_offset = b * BLOCKSIZE; int next_track = image[block_offset + TRACKLINKOFFSET]; int next_sector = image[block_offset + SECTORLINKOFFSET]; filetrack = next_track; filesector = next_sector; } return false; } /* Prints all filenames of files that use the given track */ static void print_track_usage(image_type type, const unsigned char *image, int(*blocktags)[SECTORSPERTRACK_D81], int track) { char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET] & 0xf; if (filetype != 0) { int filetrack = image[dirblock + FILETRACKOFFSET]; int filesector = image[dirblock + FILESECTOROFFSET]; bool ontrack = (type == IMAGE_D71) ? fileontrack(type, image, track, (filetrack > D64NUMTRACKS) ? filetrack - D64NUMTRACKS : filetrack + D64NUMTRACKS, filesector) : false; if (is_transwarp_file(image, dirblock)) { int start_track; int end_track; int low_track; int high_track; int filesize = transwarp_stat(type, image, dirblock, &start_track, &end_track, &low_track, &high_track); if (filesize <= 0) { continue; } ontrack = (low_track <= track) && (track <= high_track); if (ontrack == false) { continue; } filetrack = start_track; } if (ontrack || fileontrack(type, image, track, filetrack, filesector)) { unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET; if (track == filetrack) { reverse_print_on(); } printf("%c", blocktags[filetrack][filesector]); if (track == filetrack) { reverse_print_off(); } printf(": "); print_filename(stdout, filename); printf(" "); } } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); } /* Prints the BAM allocation map and returns the number of free blocks */ static int check_bam(image_type type, const unsigned char* image) { int sectorsFree = 0; int sectorsFreeOnDirTrack = 0; int sectorsOccupied = 0; int sectorsOccupiedOnDirTrack = 0; int blocktags[D81NUMTRACKS][SECTORSPERTRACK_D81]; if (verbose) { memset(blocktags, 0, sizeof blocktags); assign_blocktags(type, image, blocktags); printf("Block allocation:\n"); } int max_track = (type == IMAGE_D81) ? D81NUMTRACKS : (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) ? D64NUMTRACKS_EXTENDED : D64NUMTRACKS); for (int t = 1; t <= max_track; t++) { if (verbose) { printf(" %2d: ", t); } for (int s = 0; s < num_sectors(type, t); s++) { if (is_sector_free(type, image, t, s, 0, 0)) { if (verbose) { printf("."); } if (t != dirtrack(type)) { sectorsFree++; } else { sectorsFreeOnDirTrack++; } } else { if (verbose) { int blocktag = blocktags[t][s]; if (blocktag == 0) { blocktag = '#'; } if (blocktag >= 256) { reverse_print_on(); } printf("%c", blocktag); if (blocktag >= 256) { reverse_print_off(); } } if (t != dirtrack(type)) { sectorsOccupied++; } else { sectorsOccupiedOnDirTrack++; } } } if (type == IMAGE_D71) { for (int i = num_sectors(type, t); i < 23; i++) { if (verbose) { printf(" "); } } int t2 = t + D64NUMTRACKS; if (verbose) { printf("%2d: ", t2); } for (int s = 0; s < num_sectors(type, t2); s++) { if (is_sector_free(type, image, t2, s, 0, 0)) { if (verbose) { printf("."); } if (t2 != dirtrack(type)) { sectorsFree++; } else { /* track 53 is usually empty except the extra BAM block */ sectorsFreeOnDirTrack++; } } else { if (verbose) { printf("#"); } sectorsOccupied++; } } } for (int i = ((type == IMAGE_D81) ? 42 : 23) - num_sectors(type, t); i > 0; --i) { if (verbose) { printf(" "); } } if (verbose) { print_track_usage(type, image, blocktags, t); printf("\n"); } } if (verbose) { printf("%3d/%3d blocks free (%d/%d including dir track)\n", sectorsFree, sectorsFree + sectorsOccupied, sectorsFree + sectorsFreeOnDirTrack, sectorsFree + sectorsFreeOnDirTrack + sectorsOccupied + sectorsOccupiedOnDirTrack); } return sectorsFree; } /* Prints the filetype like the C64 when listing the directory */ static void print_filetype(int filetype) { if ((filetype & 0x80) == 0) { printf("*"); } else { printf(" "); } if(unicode == 1) { printf("%s", filetypename_uc[filetype & 0xf]); } else { printf("%s", filetypename_lc[filetype & 0xf]); } if ((filetype & 0x40) != 0) { printf("<"); } else { printf(" "); } } /* Prints the directory like the C64 when listing the directory */ static void print_directory(image_type type, unsigned char* image, int blocks_free) { unsigned char* bam = image + linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } printf("\n0 "); reverse_print_on(); printf("\""); print_petscii(bam + get_header_offset(type), 16); printf("\" "); print_petscii(bam + get_id_offset(type), 5); reverse_print_off(); if (verbose) { printf(" fn hash ASCII"); } printf("\n"); int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET]; int blocks = image[dirblock + FILEBLOCKSLOOFFSET] + 256 * image[dirblock + FILEBLOCKSHIOFFSET]; if (filetype != FILETYPEDEL) { unsigned char* filename = (unsigned char*)image + dirblock + FILENAMEOFFSET; printf("%-3d ", blocks); print_dirfilename(filename); print_filetype(filetype); if (verbose) { printf(" [$%04x] \"", filenamehash(filename)); print_filename_with_escapes(filename, FILENAMEMAXSIZE); printf("\""); } printf("\n"); } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); if(unicode == 1) { printf("%d BLOCKS FREE.\n", blocks_free); } else { printf("%d blocks free.\n", blocks_free); } /* detect and print bam message */ if((type == IMAGE_D64 || type == IMAGE_D64_EXTENDED_SPEED_DOS) && bam[BAMMESSAGEOFFSET] != 0) { printf("\nBAM message: \""); /* only checking the first byte and hoping for the best... */ int b = BAMMESSAGEOFFSET; unsigned char c; while(b < 256 && ((c = bam[b]) != 0)) { putp(c, stdout); b++; } printf("\"\n"); } } /* Performs GCR encoding on 32 bit value */ static const unsigned char NIBBLE_TO_GCR[] = { 0x0a, 0x0b, 0x12, 0x13, 0x0e, 0x0f, 0x16, 0x17, 0x09, 0x19, 0x1a, 0x1b, 0x0d, 0x1d, 0x1e, 0x15 }; static void encode_4_bytes_gcr(char* in, char* out) { out[0] = (NIBBLE_TO_GCR[(in[0] >> 4) & 0xf] << 3) | (NIBBLE_TO_GCR[ in[0] & 0xf] >> 2); /* 11111222 */ out[1] = (NIBBLE_TO_GCR[ in[0] & 0xf] << 6) | (NIBBLE_TO_GCR[(in[1] >> 4) & 0xf] << 1) | (NIBBLE_TO_GCR[ in[1] & 0xf] >> 4); /* 22333334 */ out[2] = (NIBBLE_TO_GCR[ in[1] & 0xf] << 4) | (NIBBLE_TO_GCR[(in[2] >> 4) & 0xf] >> 1); /* 44445555 */ out[3] = (NIBBLE_TO_GCR[(in[2] >> 4) & 0xf] << 7) | (NIBBLE_TO_GCR[ in[2] & 0xf] << 2) | (NIBBLE_TO_GCR[(in[3] >> 4) & 0xf] >> 3); /* 56666677 */ out[4] = (NIBBLE_TO_GCR[(in[3] >> 4) & 0xf] << 5) | NIBBLE_TO_GCR[ in[3] & 0xf]; /* 77788888 */ } /* Transwarp encoding utility functions */ static void generate_gcr_decoding_table(const unsigned char nibble_to_gcr[], int8_t gcr_to_nibble[]) { for (int i = 0; i < 32; ++i) { gcr_to_nibble[i] = -(i + 1); } for (int i = 0; i < 16; ++i) { gcr_to_nibble[nibble_to_gcr[i]] = i; } } static unsigned char even_bits(unsigned char value) { return (((value >> 6) & 1) << 3) | (((value >> 4) & 1) << 2) | (((value >> 2) & 1) << 1) | (((value >> 0) & 1) << 0); } static unsigned char odd_bits(unsigned char value) { return (((value >> 7) & 1) << 3) | (((value >> 5) & 1) << 2) | (((value >> 3) & 1) << 1) | (((value >> 1) & 1) << 0); } typedef struct transwarp_encode_context { unsigned int version; unsigned char previous; unsigned char previous1; unsigned char previous2; unsigned char accu; unsigned char carry; unsigned char recvcarry; unsigned char carry2; unsigned char sendaccu; unsigned char sendcarry; } transwarp_encode_context; static const int ENCODE[5][64] = { { 0xf6, 0xee, 0xf5, 0xed, 0x9a, 0xde, 0x96, 0xda, 0xf3, 0xea, 0xf2, 0x9e, 0x93, 0xd6, 0x92, 0xd3, 0xd2, 0xca, 0xce, 0xbe, 0xb3, 0x7e, 0xb2, 0x7d, 0xcd, 0xba, 0xcb, 0xb6, 0xae, 0x7b, 0xaa, 0x7a, 0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x73, 0x6b, 0x72, 0x6a, 0xdb, 0x9d, 0xeb, 0xd5, 0x56, 0x4e, 0x55, 0x4d, 0xbd, 0xb5, 0xbb, 0xad, 0x53, 0x4b, 0x52, 0xdd, 0xab, 0x4a, 0x9b, 0x95 }, { 0xf6, 0xee, 0xf5, 0xed, 0x9a, 0xde, 0x96, 0xda, 0xf3, 0xea, 0xf2, 0x9e, 0x93, 0xd6, 0x92, 0xef, 0xe7, 0x7c, 0x9f, 0x74, 0xe6, 0x6c, 0xdf, 0xa6, 0x9c, 0xba, 0x94, 0xb6, 0xae, 0x7b, 0xaa, 0x7a, 0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x73, 0x6b, 0x72, 0x6a, 0xdb, 0xd7, 0xeb, 0xe5, 0x56, 0x64, 0x55, 0x5c, 0xbd, 0xb5, 0xbb, 0xad, 0x65, 0x54, 0x5f, 0xdd, 0xab, 0xa7, 0x9b, 0xa5 }, { 0xa5, 0xa7, 0xa9, 0xab, 0xd5, 0xd7, 0xd9, 0xdb, 0x95, 0x99, 0x9b, 0x97, 0xe5, 0x9d, 0xeb, 0xe9 }, { 0xf6, 0xee, 0xf5, 0xed, 0x69, 0xde, 0x59, 0xda, 0xb9, 0xea, 0xb7, 0x6f, 0x57, 0xd6, 0x4f, 0xef, 0xe7, 0xca, 0xce, 0xbe, 0xe6, 0xbf, 0xdf, 0xa6, 0xcd, 0xba, 0xcb, 0xb6, 0xae, 0x7b, 0xaa, 0x7a, 0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x67, 0x6b, 0x66, 0x6a, 0xe9, 0xd7, 0xeb, 0xe5, 0x56, 0x4e, 0x55, 0x4d, 0xbd, 0xb5, 0xbb, 0xad, 0x65, 0x4b, 0x5f, 0xdd, 0xab, 0xa7, 0xa9, 0xa5 }, { 0xcf, 0xaf, 0xc9, 0x79, 0x69, 0xde, 0x59, 0xda, 0xb9, 0x77, 0xb7, 0x6f, 0x57, 0xd6, 0x4f, 0xd3, 0xd2, 0xca, 0xce, 0xbe, 0xb3, 0x7e, 0xb2, 0x7d, 0xcd, 0xba, 0xcb, 0xb6, 0xae, 0x7b, 0xaa, 0x7a, 0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x73, 0x6b, 0x72, 0x6a, 0xdb, 0xd7, 0xd9, 0xd5, 0x56, 0x4e, 0x55, 0x4d, 0xbd, 0xb5, 0xbb, 0xad, 0x53, 0x4b, 0x52, 0xdd, 0xab, 0x4a, 0xa9, 0x49 } }; static const int DECODE[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x00, 0x02, 0x12, -1, 0x14, 0x16, 0x68, -1, -1, 0x18, 0x1a, 0x12, 0x1c, 0x1e, 0x6a, -1, 0x6c, 0x24, 0x26, 0x14, 0x2c, 0x2e, 0x18, -1, -1, -1, -1, 0x16, 0x1a, 0x38, 0x3a, -1, 0x6e, 0x30, 0x32, 0x46, 0x34, 0x36, 0x70, -1, -1, 0x38, 0x3a, 0x54, 0x3c, 0x3e, 0x72, -1, 0x74, 0x40, 0x42, 0x56, 0x44, 0x46, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x68, 0x6a, 0x58, 0x80, 0x6c, 0x8a, -1, 0x82, 0x6e, 0x88, 0x5a, 0xa2, 0x70, 0x5c, -1, -1, -1, -1, -1, 0x00, 0x44, 0x02, -1, 0x08, 0x48, 0x0a, -1, 0x04, 0x4a, 0x76, -1, -1, 0x4c, 0x4e, -1, 0x06, 0x50, 0x78, -1, 0x7a, 0x52, 0x0c, -1, 0x0e, 0x54, 0x46, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x7c, 0x56, 0x58, -1, 0x5a, 0x5c, 0x7e, -1, -1, 0x5e, 0x60, -1, 0x20, 0x62, 0x22, -1, 0x28, 0x64, 0x2a, -1, 0x10, 0x66, 0x4c, -1, -1, -1, -1, -1, 0xa0, 0x4e, 0x5e, -1, 0xaa, 0x72, 0xa8, -1, 0x74, 0x76, 0x60, -1, -1, 0x78, 0x7a, -1, 0x7c, 0x7e, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static unsigned char encode_read_diff(const int encode[64], unsigned char *accu, unsigned char *carry, unsigned char value) { unsigned char value_to_encode; unsigned char target = DECODE[encode[value]] & 0x7e; for (value_to_encode = 0; value_to_encode < 64; ++value_to_encode) { unsigned char val = DECODE[encode[value_to_encode]] + *accu + *carry; if ((val & 0x7e) == target) { break; } } if (value_to_encode >= 64) { printf("Encoding error, 0x%x = 0x%x + %d + ?\n", target, *accu, *carry); for (value_to_encode = 0; value_to_encode < 64; ++value_to_encode) { unsigned char val = DECODE[encode[value_to_encode]] + *accu + *carry; printf("%d: 0x%x <- 0x%x\n", value_to_encode, val & 0x7e, DECODE[encode[value_to_encode]]); } fprintf(stderr, "ERROR: Transwarp encoding error\n"); exit(-2); } int sum = DECODE[encode[value_to_encode]] + *accu + *carry; *accu = sum; *carry = sum >= 256; unsigned char check = DECODE[encode[value]]; if ((*accu & 0x7e) != (check & 0x7e)) { fprintf(stderr, "ERROR: Transwarp encoding error, 0x%x: actual 0x%x != 0x%x expected\n", DECODE[encode[check & 0x3f]], *accu, check); exit(-3); } unsigned char temp = (*carry << 7) | (*accu >> 1); *carry = *accu & 1; *accu = (*carry << 7) | ((temp & 0xfb) >> 1); *carry = (*accu >> 6) & 1; return encode[value_to_encode]; } unsigned char encode_send_diff(unsigned char value, unsigned char *accu, unsigned char *carry) { value = (value & ~((1 << 3) | (1 << 0))) | (((value >> 3) & 1) << 0) | (((value >> 0) & 1) << 3); value ^= 0xff; int diff = value - *accu - *carry; *carry = (diff < 0); *accu = value; *accu = (((*accu >> 7) & 1) << 7) | (((*accu >> 1) & 1) << 6) | (((*accu >> 7) & 1) << 5) | (*carry << 4) | (((*accu >> 6) & 1) << 2) | (((*accu >> 5) & 1) << 1); *carry = (*accu >> 6) & 1; return diff; } static unsigned char encode_receive_diff(const transwarp_encode_context *ctx, unsigned char in, unsigned char *previous, unsigned char *carry) { int offset = (ctx->version <= 84) ? 0 : 2; int out = in - *carry - offset; int diff = out - ((*previous & 0xc0) | (out & 0x3f)); *carry = (diff < 0); out = ((out ^ *previous) & 0x3f) | diff; *previous = in; return out; } static unsigned char encode_buffer_byte(const int encode[][64], unsigned char previous, unsigned char *carry, unsigned char in, unsigned char *out) { unsigned char even = (previous >> 1) ^ in; unsigned char odd = ((*carry << 7) | (previous >> 1)) ^ in; *carry = previous & 1; out[(31 * 5) + 4] = encode[2][even_bits(even)]; out[4] = encode[2][odd_bits(odd)]; return in; } static void encode_base_bytes(const unsigned char scramble[][256], transwarp_encode_context *ctx, const unsigned char in[3], unsigned char *out) { unsigned char in0 = encode_receive_diff(ctx, in[0], &(ctx->previous), &(ctx->recvcarry)); unsigned char in1 = encode_receive_diff(ctx, in[1], &(ctx->previous), &(ctx->recvcarry)); unsigned char in2 = encode_receive_diff(ctx, in[2], &(ctx->previous), &(ctx->recvcarry)); in0 = scramble[0][in0]; in1 = scramble[1][in1]; in2 = scramble[2][in2]; unsigned char val3 = in0 & 0x3f; out[0] = encode_read_diff(ENCODE[3], &(ctx->accu), &(ctx->carry), val3); unsigned char val4 = ((in0 >> 6) | (in1 << 2)) & 0x3f; out[1] = encode_read_diff(ENCODE[4], &(ctx->accu), &(ctx->carry), val4); unsigned char val0 = ((in1 >> 4) | (in2 << 4)) & 0x3f; out[2] = encode_read_diff(ENCODE[0], &(ctx->accu), &(ctx->carry), val0); unsigned char val1 = in2 >> 2; out[3] = encode_read_diff(ENCODE[1], &(ctx->accu), &(ctx->carry), val1); } static unsigned char crc8(unsigned char value) { for (int i = 0; i < 8; ++i) { value = (value & 0x80) ? ((value << 1) ^ 0x31) : (value << 1); } return value; } static bool decode_5_bytes_gcr(const int8_t decoding_map[], const unsigned char *in, unsigned char *out) { unsigned char gcr_hi; unsigned char gcr_lo; gcr_hi = in[0] >> 3; gcr_lo = ((in[0] & 0x7) << 2) | (in[1] >> 6); int out0 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo]; gcr_hi = (in[1] & 0x3e) >> 1; gcr_lo = ((in[1] & 0x1) << 4) | (in[2] >> 4); int out1 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo]; gcr_hi = ((in[2] & 0xf) << 1) | (in[3] >> 7); gcr_lo = (in[3] & 0x7c) >> 2; int out2 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo]; gcr_hi = ((in[3] & 0x3) << 3) | (in[4] >> 5); gcr_lo = in[4] & 0x1f; int out3 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo]; out[0] = out0; out[1] = out1; out[2] = out2; out[3] = out3; return (out0 | out1 | out2 | out3) >= 0; } static int decode_gcr_block(const int8_t decoding_map[], const unsigned char *encoded, unsigned char decoded[]) { bool ok = decode_5_bytes_gcr(decoding_map, encoded, decoded); decoded[0] = decoded[1]; decoded[1] = decoded[2]; decoded[2] = decoded[3]; int computed_checksum = decoded[0] ^ decoded[1] ^ decoded[2]; int i = 5; for (int j = 3; i < 320; i += 5, j += 4) { ok &= decode_5_bytes_gcr(decoding_map, encoded + i, decoded + j); computed_checksum ^= decoded[j] ^ decoded[j + 1] ^ decoded[j + 2] ^ decoded[j + 3]; } unsigned char last_byte[4]; ok &= decode_5_bytes_gcr(decoding_map, encoded + i, last_byte); decoded[255] = last_byte[0]; computed_checksum ^= last_byte[0]; computed_checksum = ok ? computed_checksum : -1; return ok ? computed_checksum : -1; } static int encode_transwarp_block(const unsigned char scramble[][256], const int8_t gcr_to_nibble[32], transwarp_encode_context* ctx, const unsigned char *indata, int filepos, unsigned char encoded[325]) { const unsigned char *unencoded = indata + filepos; unsigned char data[TRANSWARPBLOCKSIZE]; if (filepos < 2) { for (int i = filepos, j = 0; j < TRANSWARPBLOCKSIZE; ++i, ++j) { data[j] = (i < 0) ? ' ' : ((i < 2) ? 0 : indata[i]); } unencoded = data; } unsigned char semiencoded[TRANSWARPBUFFERBLOCKSIZE]; for (int i = TRANSWARPBUFFERBLOCKSIZE - 1; i >= 0; --i) { unsigned char value = encode_receive_diff(ctx, unencoded[TRANSWARPBASEBLOCKSIZE + i], &(ctx->previous2), &(ctx->carry2)); value = scramble[3][value]; semiencoded[i] = encode_send_diff(value, &(ctx->sendaccu), &(ctx->sendcarry)); } unsigned char buffer_previous = ctx->previous1; unsigned char buffer_carry = 0; for (int i = 0; i < TRANSWARPBUFFERBLOCKSIZE; ++i) { int shuffle = (TRANSWARPBUFFERBLOCKSIZE - 1) - (i / 2) - ((i & 1) ? ((TRANSWARPBUFFERBLOCKSIZE / 2) + 1) : 0); unsigned char value = semiencoded[shuffle]; buffer_previous = encode_buffer_byte(ENCODE, buffer_previous, &buffer_carry, value, encoded + 3 + (5 * (TRANSWARPBUFFERBLOCKSIZE - 1 - shuffle))); } unsigned char previous = unencoded[TRANSWARPBASEBLOCKSIZE - 1]; const int CRCSTEP = 8; for (int i = 0; i < TRANSWARPBASEBLOCKSIZE; i += CRCSTEP) { previous = crc8(previous); previous ^= unencoded[i]; } previous = crc8(previous); if (filepos > ((21 * TRANSWARPBLOCKSIZE) + 2)) { const int BACKCHECKOFFS = (21 * TRANSWARPBLOCKSIZE) + TRANSWARPBUFFERBLOCKSIZE; previous ^= indata[filepos - BACKCHECKOFFS]; previous = crc8(previous); previous ^= indata[filepos - BACKCHECKOFFS + TRANSWARPBUFFERBLOCKSIZE - 1]; previous = crc8(previous); } char head_data[4] = { 7, 0, 0, 0 }; encode_4_bytes_gcr(head_data, (char *) encoded); char tail_data[4] = { 0, 0, 0, 0 }; encode_4_bytes_gcr(tail_data, (char *) encoded + 320); unsigned char accu = ctx->previous; unsigned char carry = 0; for (int i = 0; i < TRANSWARPBASEBLOCKSIZE; ++i) { encode_receive_diff(ctx, unencoded[i], &accu, &carry); } unsigned char receive_checksum = (-previous - carry); unsigned char checksum = receive_checksum ^ (buffer_previous >> 1); unsigned char odd = odd_bits(checksum); odd ^= (buffer_carry << 3); static const unsigned char ENCODE_TOP[] = { 0x05, 0x07, 0x0d, 0x0b }; encoded[2] = (encoded[2] & 0xf0) | ENCODE_TOP[odd & 0x3]; encoded[317] = ENCODE[2][even_bits(checksum)]; encoded[322] = ENCODE[2][odd & 0xc]; unsigned char top_2_bits = encoded[2]; unsigned char middle_4_bits = encoded[317]; unsigned char bottom_2_bits = encoded[322]; ctx->recvcarry = 0; ctx->accu = 0; ctx->carry = 0; for (int i = 0, j = 0; i < TRANSWARPBASEBLOCKSIZE; i += 3, j += 5) { encode_base_bytes(scramble, ctx, unencoded + i, encoded + 3 + j); ctx->accu = 8; ctx->carry = 0; unsigned char target = encoded[3 + j + 4]; unsigned char target_accu = DECODE[target]; unsigned char target_check = ENCODE[2][odd_bits(target_accu)]; if (target != target_check) { fprintf(stderr, "ERROR: Transwarp encoding error, [%d] 0x%x != 0x%x <- 0x%x\n", i, target, target_check, DECODE[target]); exit(-4); } unsigned char store = ctx->accu ^ target_accu; encoded[3 + j + 4] = ENCODE[2][odd_bits(store)]; unsigned char stored = DECODE[encoded[3 + j + 4]]; ctx->accu ^= stored; if (ctx->accu != target_accu) { fprintf(stderr, "ERROR: Transwarp encoding error, [%d] actual 0x%x != 0x%x expected <- 0x%x\n", i, ctx->accu, target_accu, stored); exit(-5); } } if (carry != ctx->recvcarry) { fprintf(stderr, "ERROR: Transwarp encoding error, carry %d != %d recvcarry\n", carry, ctx->recvcarry); exit(-6); } unsigned char top_fix = encoded[2] ^ (DECODE[encoded[322]] & 0xf); top_fix = ((top_fix >> 2) & 2) | ((top_fix >> 1) & 1); encoded[2] = (encoded[2] & 0xf0) | ENCODE_TOP[top_fix]; encoded[322] = (encoded[322] & 0xf0) | 0x5; unsigned char block_checksum; block_checksum = DECODE[middle_4_bits]; block_checksum = (block_checksum >> 1) | (buffer_carry << 7); block_checksum ^= DECODE[bottom_2_bits]; block_checksum ^= (top_2_bits & 0xa); if (block_checksum != checksum) { fprintf(stderr, "ERROR: Transwarp encoding error, actual 0x%x != 0x%x expected, 0x%x -> [0x%x] -> 0x%x -> 0x%x -> [0x%x]\n", block_checksum, checksum, checksum & 0xaa, ((checksum & 0xaa) >> 1) | (checksum & 0xaa), odd, ENCODE[2][odd], DECODE[encoded[317]]); exit(-1); } unsigned char gcr_decoded[4]; bool ok = decode_5_bytes_gcr(gcr_to_nibble, encoded + 320, gcr_decoded); int gcr_checksum = ok ? gcr_decoded[1] : -1; unsigned char decoded[256]; int computed_checksum = decode_gcr_block(gcr_to_nibble, encoded, decoded); ok &= decode_5_bytes_gcr(gcr_to_nibble, encoded, gcr_decoded); gcr_decoded[1] ^= gcr_checksum ^ computed_checksum; encode_4_bytes_gcr((char *) gcr_decoded, (char *) encoded); return ok == 0; } static void permute(unsigned char *key, int len, int *set) { for (int i = 0; i < (len - 1); ++i) { int divisor = len - i; int remainder = 0; for (int i = TRANSWARPKEYSIZE - 12; i >= 0; --i) { int dividend = (remainder << 8) | key[i]; remainder = dividend % divisor; key[i] = dividend / divisor; } int n = set[remainder]; set[remainder] = set[divisor - 1]; set[divisor - 1] = n; } } /* Write file to disk using Transwarp encoding */ static unsigned long long write_transwarp_file(image_type type, unsigned char *image, imagefile *file, unsigned char *filedata, int *filesize, unsigned int version, bool transwarp_bootfile_fits_on_dir_track) { file->size = *filesize - 2; unsigned int track = DIRTRACK_D41_D71 - 1; if ((file->mode & MODE_MIN_TRACK_MASK) > 0) { /* for Transwarp files, a set minimum track is the file's starting track */ track = (file->mode & MODE_MIN_TRACK_MASK) >> MODE_MIN_TRACK_SHIFT; } else { /* allocate */ bool free_tracks[40]; for (unsigned int t = 1; t <= image_num_tracks(type); ++t) { bool track_free = true; for (int sector = 0; sector < num_sectors(type, t); ++sector) { if (is_sector_free(type, image, t, sector, 0 /* numdirblocks */, 0 /* dir_sector_interleave */) == false) { track_free = false; break; } } free_tracks[t - 1] = track_free; } /* below dir track */ while (track > 0) { if (free_tracks[track - 1]) { int filesize = file->size; int t = track; while ((filesize > 0) && (t > 0)) { if (free_tracks[t - 1] == false) { break; } filesize -= (TRANSWARPBLOCKSIZE * num_sectors(type, t)); if (filesize >= 0) { --t; } } if (filesize <= 0) { break; } } --track; } if (track <= 0) { /* above dir track */ track = DIRTRACK_D41_D71 + (transwarp_bootfile_fits_on_dir_track ? 1 : 2); while (track <= image_num_tracks(type)) { if (free_tracks[track - 1]) { int filesize = file->size; int t = track; while (filesize > 0) { if (free_tracks[t - 1] == false) { break; } filesize -= (TRANSWARPBLOCKSIZE * num_sectors(type, t)); if (filesize >= 0) { ++t; } } if (filesize <= 0) { break; } } ++track; } } } file->track = track; file->sector = 0; int8_t gcr_to_nibble[32]; generate_gcr_decoding_table(NIBBLE_TO_GCR, gcr_to_nibble); unsigned char key[TRANSWARPKEYSIZE]; memset(key, 0, sizeof key); if (file->have_key != 0) { if ((filedata[0] == 0x01) && (filedata[1] == 0x08)) { srand((unsigned int) time(NULL)); unsigned int linelink = ((filedata[3] << 8) | filedata[2]) - 0x0801 + 2; if ((linelink > 0) && ((linelink - 2) < (unsigned int) file->size) && ((filedata[linelink - 1] | filedata[linelink] | filedata[linelink + 1]) == 0)) { while (filedata[linelink] == 0) { filedata[linelink] = rand(); } } filedata[2] = rand(); while ((filedata[3] == 0) || (filedata[3] == 8)) { filedata[3] = rand(); } int file_size = file->size; int filetrack = track; while (file_size > 0) { file_size -= (TRANSWARPBLOCKSIZE * num_sectors(type, filetrack)); if (file_size > 0) { filetrack = (filetrack < DIRTRACK_D41_D71) ? (filetrack - 1) : (filetrack + 1); } } int spare_blocks = (0 - file_size) / TRANSWARPBLOCKSIZE; int spare_bytes = TRANSWARPBLOCKSIZE - (file->size % TRANSWARPBLOCKSIZE); spare_bytes = (spare_blocks * TRANSWARPBLOCKSIZE) + ((spare_bytes != TRANSWARPBLOCKSIZE) ? spare_bytes : 0); while (spare_bytes > (0x0801 - 0x0400)) { spare_bytes -= TRANSWARPBLOCKSIZE; } if (spare_bytes > 0) { int loadaddress = (filedata[1] << 8) | filedata[0]; loadaddress -= spare_bytes; filedata[0] = loadaddress; filedata[1] = loadaddress >> 8; memmove(filedata + 2 + spare_bytes, filedata + 2, file->size); filedata[spare_bytes + 1] = 0; for (int i = 2; i <= spare_bytes; ++i) { filedata[i] = rand(); } file->size += spare_bytes; *filesize += spare_bytes; } } memcpy(key, file->key, sizeof key); for (int round = TRANSWARPKEYHASHROUNDS; round > 0; --round) { for (int i = 0; i < (TRANSWARPKEYSIZE - 1); ++i) { key[i] ^= key[i + 1]; } for (int i = TRANSWARPKEYSIZE - 1; i >= 0; --i) { int product = key[i] * 0x6b; key[i] = product; int msb = product >> 8; for (int j = i + 1; j < TRANSWARPKEYSIZE; ++j) { msb += key[j]; key[j] = msb; msb >>= 8; } } int sum = round; for (int i = 0; i < TRANSWARPKEYSIZE; ++i) { sum += key[i]; key[i] = sum; sum >>= 8; } } } unsigned long long dirdatakey = (key[TRANSWARPKEYSIZE - 1] * (1ULL << 56)) + (key[TRANSWARPKEYSIZE - 2] * (1ULL << 48)) + (key[TRANSWARPKEYSIZE - 3] * (1ULL << 40)) + (key[TRANSWARPKEYSIZE - 4] * (1ULL << 32)) + (key[TRANSWARPKEYSIZE - 5] * (1ULL << 24)) + (key[TRANSWARPKEYSIZE - 6] * (1ULL << 16)) + (key[TRANSWARPKEYSIZE - 7] * (1ULL << 8)) + key[TRANSWARPKEYSIZE - 8]; int initial_buffer_store_value = key[TRANSWARPKEYSIZE - 9]; int initial_buffer_recvaccu_value = key[TRANSWARPKEYSIZE - 10]; int initial_block_recvaccu_value = key[TRANSWARPKEYSIZE - 11]; unsigned char scramble[4][256]; for (int i = 0; i < 4; ++i) { int set[] = { 0, 2, 4, 1, 3, 5 }; if (file->have_key) { permute(key, 6, set); } int set2[3][4]; for (int j = 0; j < 3; ++j) { int set3[] = { 0, 1, 2, 3 }; if (file->have_key) { permute(key, 4, set3); } for (int k = 0; k < 4; ++k) { for (int l = 0; l < 4; ++l) { if (k == set3[l]) { set2[j][k] = l; break; } } } } for (int j = 0; j < 256; ++j) { unsigned char scrambled = (set2[0][(((j >> set[0]) & 1) << 0) | (((j >> set[3]) & 1) << 1)] << 0) | (set2[1][(((j >> set[1]) & 1) << 0) | (((j >> set[4]) & 1) << 1)] << 2) | (set2[2][(((j >> set[2]) & 1) << 0) | (((j >> set[5]) & 1) << 1)] << 4) | (j & 0xc0); scramble[i][j] = scrambled; } } int sectors[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; if (file->have_key != 0) { permute(key, 17, sectors); } transwarp_encode_context ctx; memset(&ctx, 0, sizeof ctx); ctx.version = version; ctx.previous1 = initial_buffer_store_value; bool done = false; int block_index = 0; int total_blocks = 0; int filepos = 2; for (; !done; (track >= DIRTRACK_D41_D71) ? ++track : --track) { if ((track < 1) || (track > image_num_tracks(type))) { fprintf(stderr, "ERROR: Disk full (track %d out of range) while writing Transwarp file ", track); print_filename(stderr, file->pfilename); fprintf(stderr, "\n"); exit(-4); } int next_track_pos = filepos + (num_sectors(type, track) * TRANSWARPBLOCKSIZE); bool last_track = (next_track_pos >= *filesize); if (last_track) { int sector = 0; for (int s = 0; s < num_sectors(type, track); ++s) { sectors[s] = sector; if ((filepos + ((sector + 1) * TRANSWARPBLOCKSIZE)) >= *filesize) { sector = 0; } else { ++sector; } } } int next_track_block_index = block_index + num_sectors(type, track); int trackpos = filepos; transwarp_encode_context trackctx = ctx; for (int sector = 0; sector < num_sectors(type, track); ++sector) { if (is_sector_free(type, image, track, sector, 0 /* numdirblocks */, 0 /* dir_sector_interleave */) == false) { fprintf(stderr, "ERROR: t%d/s%d not free for Transwarp file ", track, sector); print_filename(stderr, file->pfilename); fprintf(stderr, "\n"); check_bam(type, image); exit(-5); } bool last_block = false; int pos = trackpos + (sectors[sector] * TRANSWARPBLOCKSIZE); if ((pos + TRANSWARPBLOCKSIZE) >= *filesize) { pos = *filesize - TRANSWARPBLOCKSIZE; last_block = true; } unsigned char encoded[320 + 5]; unsigned char previous = block_index + sectors[sector]; previous ^= initial_block_recvaccu_value; ctx.previous = previous; ctx.previous2 = initial_buffer_recvaccu_value; int error = encode_transwarp_block((const unsigned char (*)[256]) scramble, gcr_to_nibble, &ctx, filedata, pos, encoded); if (error) { fprintf(stderr, "ERROR: encoding error on t%d/s%d\n", track, sector); exit(-6); } unsigned char decoded[256]; int checksum = decode_gcr_block(gcr_to_nibble, encoded, decoded); if (checksum < 0) { fprintf(stderr, "ERROR: decoding error on t%d/s%d\n", track, sector); exit(-7); } int offset = linear_sector(type, track, sector) * BLOCKSIZE; memcpy(image + offset, decoded, BLOCKSIZE); ++total_blocks; if (last_block) { ctx = trackctx; done = true; } mark_sector(type, image, track, sector, 0 /* not free */); } filepos = next_track_pos; block_index = next_track_block_index; file->last_track = track; } file->nrSectors = total_blocks; return dirdatakey; } /* Find consecutive tracks for Transwarp file, returns wasted blocks, or -1 if file does not fit */ static int allocate_transwarp(int size, const int sectors[], int num_tracks, int *start_track, int *next_track) { *start_track = *next_track; if(*start_track < DIRTRACK_D41_D71 - 1) { while(*start_track >= 0) { int s = size; int t = *start_track; while((t >= 0) && sectors[t]) { s -= sectors[t]; if(s <= 0) { *next_track = t ? t - 1 : DIRTRACK_D41_D71 + 1; return -s; } t--; } *start_track = t - 1; } *start_track = DIRTRACK_D41_D71 + 1; } while(*start_track < num_tracks) { int s = size; int t = *start_track; while((t < num_tracks) && sectors[t]) { s -= sectors[t]; if(s <= 0) { *next_track = t + 1; return -s; } t++; } *start_track = t + 1; } return -1; } /* Calculate wasted space for Transwarp files, returns false if files do not fit */ static bool calculate_waste(const int size[], int num_files, const int sectors[], int num_tracks, int order[], int *waste, int start_tracks[]) { int next_track = DIRTRACK_D41_D71 - 2; *waste = 0; for(int f = 0; f < num_files; f++) { int w = allocate_transwarp(size[order[f]], sectors, num_tracks, &start_tracks[f], &next_track); if(w < 0) { return false; } *waste += w; } return true; } /* Find order of Transwarp files with minimal wasted space, returns wasted sectors, or MAXFREESECTORS if the files do not fit */ static int find_best_permutation(const int size[], int num_files, const int sectors[], int num_tracks, int start_tracks[]) { int c[MAXPERMUTEDFILES]; int o[MAXPERMUTEDFILES]; int t[MAXPERMUTEDFILES]; for(int i = 0; i < num_files; i++) { c[i] = 0; o[i] = i; } int min_waste; if(!calculate_waste(size, num_files, sectors, num_tracks, o, &min_waste, start_tracks)) { min_waste = MAXFREESECTORS; /* everything is better than this */ } int original = min_waste; int i = 1; /* Heap's algorithm for permutation */ while(i < num_files) { if(c[i] < i) { int w; if((i & 1) == 0) { int tmp = o[0]; o[0] = o[i]; o[i] = tmp; /* swap o[0] and o[i] */ } else { int tmp = o[c[i]]; o[c[i]] = o[i]; o[i] = tmp; /* swap o[c[i]] and o[i] */ } c[i]++; i = 1; if(calculate_waste(size, num_files, sectors, num_tracks, o, &w, t) && (w < min_waste)) { min_waste = w; for(int j = 0; j < num_files; j++) { start_tracks[o[j]] = t[j]; } } } else { c[i] = 0; i++; } } if(verbose) { if(original == MAXFREESECTORS) { printf("\nTranswarp reordering made files fit on disk\n"); } else { printf("\nTranswarp reordering saved %d blocks\n", original - min_waste); } } return min_waste; } /* Reorder files, find best Transwarp file order and set start tracks accordingly */ static void optimize_transwarp_allocation(image_type type, unsigned char *image, imagefile *files, int num_files, int order[]) { /* find free tracks */ int sectors[40]; /* size is number of tracks for extended D64 */ unsigned int num_tracks = image_num_tracks(type); for (unsigned int t = 1; t <= num_tracks; ++t) { int ns = num_sectors(type, t); bool track_free = true; for (int sector = 0; sector < ns; ++sector) { if (is_sector_free(type, image, t, sector, 0 /* numdirblocks */, 0 /* dir_sector_interleave */) == false) { track_free = false; break; } } sectors[t-1] = track_free ? ns : 0; } /* sort files */ int o = 0; for(int f = 0; f < num_files; f++) { /* Transwarp files with -t */ int start_track = (files[f].mode & MODE_MIN_TRACK_MASK) >> MODE_MIN_TRACK_SHIFT; if((files[f].filetype & FILETYPETRANSWARPMASK) && (start_track > 0)) { start_track--; /* convert to array index */ order[o++] = f; /* block tracks */ int s = 0; struct stat st; if (stat((char*)files[f].alocalname, &st) == 0) { s = ((int)st.st_size - 2 - 1) / TRANSWARPBLOCKSIZE + 1; /* number of Transwarp blocks, rounded up */ } unsigned int track_idx = start_track; while(s > 0) { s -= sectors[track_idx]; sectors[track_idx] = 0; if(track_idx > DIRTRACK_D41_D71 - 2) { track_idx++; if(track_idx == num_tracks) { return; /* give up, let actual file allocation deal with the error */ } } else if(track_idx == 0) { track_idx = DIRTRACK_D41_D71; } else { track_idx--; } } } } int first_transwarp = o; int sizes[MAXPERMUTEDFILES]; int num_transwarp = 0; for(int f = 0; f < num_files; f++) { /* other Transwarp files */ if((files[f].filetype & FILETYPETRANSWARPMASK) && ((files[f].mode & MODE_MIN_TRACK_MASK) <= 0)) { order[o++] = f; if(num_transwarp < MAXPERMUTEDFILES) { struct stat st; if (stat((char*)files[f].alocalname, &st) == 0) { sizes[num_transwarp] = ((int)st.st_size - 2 - 1) / TRANSWARPBLOCKSIZE + 1; /* number of Transwarp blocks, rounded up */ } num_transwarp++; } } } for(int f = 0; f < num_files; f++) { /* normal files */ if(!(files[f].filetype & FILETYPETRANSWARPMASK)) { order[o++] = f; } } int start_tracks[MAXPERMUTEDFILES]; find_best_permutation(sizes, num_transwarp, sectors, num_tracks, start_tracks); for(int f = 0; f < num_transwarp; f++) { files[order[first_transwarp + f]].mode |= ((start_tracks[f]+1) << MODE_MIN_TRACK_SHIFT); } } /* Write files to disk */ static void write_files(image_type type, unsigned char *image, imagefile *files, int num_files, int usedirtrack, int dirtracksplit, int shadowdirtrack, int numdirblocks, int dir_sector_interleave, int allocation_strategy) { unsigned char sector = 0; int bytes_to_write = 0; int lastTrack = 1; int lastSector = sector; int lastOffset = linear_sector(type, lastTrack, lastSector) * BLOCKSIZE; int lastMinTrack = 1; bool transwarp_bootfile_fits_on_dir_track = false; int *allocation_order = NULL; if (num_files > 0) { /* make sure the first file already takes first sector per track into account */ sector = (type == IMAGE_D81) ? 0 : files[0].first_sector_new_track; /* indirection to allow arbitrary disk allocation order */ allocation_order = calloc(num_files, sizeof(int)); if(allocation_order == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } for(int i = 0; i < num_files; i++) { allocation_order[i] = i; } if(allocation_strategy != 0) { optimize_transwarp_allocation(type, image, files, num_files, allocation_order); } } int transwarp_version = 100; for (int i = 0; i < num_files; i++) { imagefile *file = files + allocation_order[i]; if ((file->mode & MODE_TRANSWARPBOOTFILE) != 0) { int fileSize = 0; struct stat st; if (stat((char*)file->alocalname, &st) == 0) { fileSize = (int)st.st_size; } int version_major; int version_minor; if (sscanf((char *) basename(file->alocalname), "transwarp v%d.%d", &version_major, &version_minor) == 2) { transwarp_version = (version_major * 100) + version_minor; } int num_blocks = (fileSize / 254) + ((fileSize % 254 == 0) ? 0 : 1); transwarp_bootfile_fits_on_dir_track = (num_blocks <= 17); break; } } for (int i = 0; i < num_files; i++) { imagefile *file = files + allocation_order[i]; unsigned char track = 1; /* reset start search track for every file, as Transwarp and -r might leave free blocks below */ if (type == IMAGE_D81) { file->sectorInterleave = 1; /* caught in command line parsing anyway, but does not hurt */ } int file_usedirtrack = usedirtrack; int file_numdirblocks = numdirblocks; if ((file->mode & MODE_NOFILE)) { if (file->nrSectorsShown == -1) { file->nrSectorsShown = file->nrSectors; } file->track = 0; file->sector = 0; int entryOffset = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[entryOffset + FILETRACKOFFSET] = file->track; image[entryOffset + FILESECTOROFFSET] = file->sector; image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectorsShown & 255; image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectorsShown >> 8; if (shadowdirtrack > 0) { entryOffset = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[entryOffset + FILETRACKOFFSET] = file->track; image[entryOffset + FILESECTOROFFSET] = file->sector; image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectors & 255; image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectors >> 8; } } else if (!(file->mode & MODE_LOOPFILE)) { /* loop files are handled later */ int fileSize = 0; struct stat st; if (stat((char*)file->alocalname, &st) == 0) { fileSize = (int)st.st_size; } if ((file->mode & MODE_TRANSWARPBOOTFILE) != 0) { file_usedirtrack = true; file_numdirblocks = transwarp_bootfile_fits_on_dir_track ? 2 : 4; file->sectorInterleave = -4; file->mode = (file->mode & ~MODE_BEGINNING_SECTOR_MASK) | (10 + 1); file->first_sector_new_track = 10; track = DIRTRACK_D41_D71; } unsigned char* filedata = (unsigned char*)calloc(fileSize + ((file->filetype & FILETYPETRANSWARPMASK) ? (21 * TRANSWARPBLOCKSIZE) : 0), sizeof(unsigned char)); if (filedata == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } FILE* f = fopen((char*)file->alocalname, "rb"); if (f == NULL) { fprintf(stderr, "ERROR: Could not open file \"%s\" for reading\n", file->alocalname); exit(-1); } if (fread(filedata, fileSize, 1, f) != 1) { fprintf(stderr, "ERROR: Unexpected filesize when reading %s\n", file->alocalname); exit(-1); } fclose(f); if ((!(file->filetype & FILETYPETRANSWARPMASK)) && ((file->mode & MODE_MIN_TRACK_MASK) > 0)) { int minTrack = (file->mode & MODE_MIN_TRACK_MASK) >> MODE_MIN_TRACK_SHIFT; if (lastMinTrack != minTrack) { lastMinTrack = minTrack; track = minTrack; /* note that track may be smaller than lastTrack now */ if (track > image_num_tracks(type)) { fprintf(stderr, "ERROR: Invalid minimum track %u for file %s (", track, file->alocalname); print_filename(stderr, file->pfilename); fprintf(stderr, ") specified\n"); exit(-1); } while ((!file_usedirtrack) && ((track == dirtrack(type)) || (track == shadowdirtrack) || ((type == IMAGE_D71) && (track == (D64NUMTRACKS + dirtrack(type)))))) { /* .d71 track 53 is usually empty except the extra BAM block */ ++track; /* skip dir track */ } if (abs(((int) track) - lastTrack) > 1) { /* previous file's last track and this file's beginning track have tracks in between */ sector = (type == IMAGE_D81) ? 0 : file->first_sector_new_track; } } } else { lastMinTrack = 0; } if ((file->mode & MODE_BEGINNING_SECTOR_MASK) > 0) { sector = (file->mode & MODE_BEGINNING_SECTOR_MASK) - 1; } if ((!(file->filetype & FILETYPETRANSWARPMASK)) && (((file->mode & MODE_SAVETOEMPTYTRACKS) != 0) || ((file->mode & MODE_FITONSINGLETRACK) != 0))) { /* find first empty track */ int found = 0; while (!found) { for (int s = 0; s < num_sectors(type, track); s++) { if (is_sector_free(type, image, track, s, file_usedirtrack ? file_numdirblocks : 0, dir_sector_interleave)) { if (s == (num_sectors(type, track) - 1)) { found = 1; /* In first pass, use sector as left by previous file (or as set by -b) to reach first file block quickly. */ /* Claus: according to Krill, on real HW tracks are not aligned anyway, so it does not make a difference. */ /* Emulators tend to reset the disk angle on track changes, so this should rather be 3. */ if (sector >= num_sectors(type, track)) { if ((file->mode & MODE_BEGINNING_SECTOR_MASK) > 0) { fprintf(stderr, "ERROR: Invalid beginning sector %u on track %u for file %s (", sector, track, file->alocalname); print_filename(stderr, file->pfilename); fprintf(stderr, ") specified\n"); exit(-1); } sector %= num_sectors(type, track); } } } else { int prev_track = track; if (file->mode & MODE_SAVECLUSTEROPTIMIZED) { if (track > D64NUMTRACKS) { int next_track = track - D64NUMTRACKS + 1; /* to next track on first side */ if (next_track < D64NUMTRACKS) { track = next_track; } else { ++track; /* disk full */ } } else { track += D64NUMTRACKS; /* to same track on second side */ } } else { ++track; } while ((!file_usedirtrack) && ((track == dirtrack(type)) || (track == shadowdirtrack) || ((type == IMAGE_D71) && (track == D64NUMTRACKS + dirtrack(type))))) { /* .d71 track 53 is usually empty except the extra BAM block */ ++track; /* skip dir track */ } if (file->mode & MODE_FITONSINGLETRACK) { int file_size = fileSize; int first_sector = -1; for (int s = 0; s < num_sectors(type, prev_track); s++) { if (is_sector_free(type, image, prev_track, s, file_usedirtrack ? file_numdirblocks : 0, dir_sector_interleave)) { if (first_sector < 0) { first_sector = s; } file_size -= BLOCKSIZE + BLOCKOVERHEAD; if (file_size <= 0) { found = 1; track = prev_track; sector = first_sector; break; } } } } if (track > image_num_tracks(type)) { fprintf(stderr, "ERROR: Disk full, file %s (", file->alocalname); print_filename(stderr, file->pfilename); fprintf(stderr, ")\n"); exit(-1); } break; } } /* for each sector on track */ if ((track == (lastTrack + 2)) && ((file->mode & MODE_BEGINNING_SECTOR_MASK) == 0)) { /* previous file's last track and this file's beginning track have tracks in between now */ sector = 0; } } /* while not found */ } if ((file->mode & MODE_BEGINNING_SECTOR_MASK) > 0) { if (sector != ((file->mode & MODE_BEGINNING_SECTOR_MASK) - 1)) { fprintf(stderr, "ERROR: Specified beginning sector of file %s (", file->alocalname); print_filename(stderr, file->pfilename); fprintf(stderr, ") not free on track %u\n", track); exit(-1); } } /* found start track, now save file */ if (type == IMAGE_D81) { sector = 0; } int byteOffset = 0; int bytesLeft = fileSize; unsigned long long key0 = 0; if (file->filetype & FILETYPETRANSWARPMASK) { key0 = write_transwarp_file(type, image, file, filedata, &fileSize, transwarp_version, transwarp_bootfile_fits_on_dir_track); bytesLeft = 0; } while (bytesLeft > 0) { /* Find free track & sector, starting from current T/S forward one revolution, then the next track etc... skip dirtrack (unless -t is active) */ /* If the file didn't fit before dirtrack then restart on dirtrack + 1 and try again (unless -t is active). */ /* If the file didn't fit before track 36/41/71 then the disk is full. */ int blockfound = 0; int findSector = 0; while (!blockfound) { /* find spare block on the current track */ for (int s = sector; s < sector + num_sectors(type, track); s++) { findSector = s % num_sectors(type, track); if (is_sector_free(type, image, track, findSector, file_usedirtrack ? file_numdirblocks : 0, dir_sector_interleave)) { blockfound = 1; break; } } if (!blockfound) { /* find next track, use some magic to make up for track seek delay */ int seek_delay = 1; if (file->mode & MODE_SAVECLUSTEROPTIMIZED) { if (track > D64NUMTRACKS) { track = track - D64NUMTRACKS + 1; } else { track += D64NUMTRACKS; seek_delay = 0; /* switching to the other side, no head movement */ } } else { ++track; } if (type == IMAGE_D81) { sector = 0; } else if (file->first_sector_new_track < 0) { sector -= file->first_sector_new_track; } else if ((file->sectorInterleave < 0) && ((file->mode & MODE_TRANSWARPBOOTFILE) == 0)) { sector += seek_delay; } else { sector = file->first_sector_new_track; } sector %= num_sectors(type, track); findSector = sector; while ((!file_usedirtrack) && ((track == dirtrack(type)) || (track == shadowdirtrack) || ((type == IMAGE_D71) && (track == D64NUMTRACKS + dirtrack(type))))) { /* .d71 track 53 is usually empty except the extra BAM block */ /* Delete old fragments and restart file */ if (!dirtracksplit) { if (file->nrSectors > 0) { int deltrack = file->track; int delsector = file->sector; while (deltrack != 0) { int b = linear_sector(type, deltrack, delsector); if(b < 0) { break; } int offset = b * BLOCKSIZE; mark_sector(type, image, deltrack, delsector, 1 /* free */); deltrack = image[offset + 0]; delsector = image[offset + 1]; memset(image + offset, 0, BLOCKSIZE); } } bytesLeft = fileSize; byteOffset = 0; file->nrSectors = 0; } ++track; } if (track > image_num_tracks(type)) { if (verbose) { print_file_allocation(type, image, files, num_files); check_bam(type, image); } fprintf(stderr, "ERROR: Disk full, file %s (", file->alocalname); print_filename(stderr, file->pfilename); fprintf(stderr, ")\n"); free(filedata); exit(-1); } } } /* while not block found */ sector = findSector; int offset = linear_sector(type, track, sector) * BLOCKSIZE; if (bytesLeft == fileSize) { file->track = track; file->sector = sector; lastTrack = track; lastSector = sector; lastOffset = offset; } else { image[lastOffset + 0] = track; image[lastOffset + 1] = sector; } /* write sector */ bytes_to_write = min(BLOCKSIZE - BLOCKOVERHEAD, bytesLeft); memset(image + offset + 2, 0, 254); memcpy(image + offset + 2, filedata + byteOffset, bytes_to_write); bytesLeft -= bytes_to_write; byteOffset += bytes_to_write; lastTrack = track; lastSector = sector; lastOffset = offset; mark_sector(type, image, track, sector, 0 /* not free */); if (num_sectors(type, track) <= abs(file->sectorInterleave)) { fprintf(stderr, "ERROR: Invalid interleave %d on track %u (%d sectors), file %s (", file->sectorInterleave, track, num_sectors(type, track), file->alocalname); print_filename(stderr, file->pfilename); fprintf(stderr, ")\n"); exit(-1); } sector += abs(file->sectorInterleave); sector %= num_sectors(type, track); file->nrSectors++; } /* while bytes left */ if (!(file->filetype & FILETYPETRANSWARPMASK)) { image[lastOffset + 0] = 0x00; image[lastOffset + 1] = bytes_to_write + 1; } /* update directory entry */ int entryOffset = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[entryOffset + FILETRACKOFFSET] = file->track; image[entryOffset + FILESECTOROFFSET] = file->sector; if (file->filetype & FILETYPETRANSWARPMASK) { image[entryOffset + FILETRACKOFFSET] = 0; image[entryOffset + FILESECTOROFFSET] = 0; image[entryOffset + TRANSWARPSIGNATROFFSLO] = TRANSWARPSIGNATURELO; image[entryOffset + TRANSWARPSIGNATROFFSHI] = TRANSWARPSIGNATUREHI; image[entryOffset + TRANSWARPTRACKOFFSET] = file->track; image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectors; image[entryOffset + FILEBLOCKSHIOFFSET] = (file->nrSectors >> 8); if (image[entryOffset + FILEBLOCKSHIOFFSET] > 0) { fprintf(stderr, "ERROR: Transwarp file \"%s\" is %d > 255 blocks big\n", file->alocalname, file->nrSectors); exit(-8); } int loadaddress = (filedata[1] << 8) | filedata[0]; image[entryOffset + LOADADDRESSLOOFFSET] = loadaddress; image[entryOffset + LOADADDRESSHIOFFSET] = (loadaddress >> 8); int endaddress = loadaddress + fileSize - 2; image[entryOffset + ENDADDRESSLOOFFSET] = endaddress; image[entryOffset + ENDADDRESSHIOFFSET] = (endaddress >> 8); unsigned char file_checksum = 0xff; for (int i = 2; i < fileSize; ++i) { file_checksum ^= filedata[i]; file_checksum = crc8(file_checksum); } image[entryOffset + FILECHECKSUMOFFSET] = file_checksum; image[entryOffset + DIRDATACHECKSUMOFFSET] = 0; image[entryOffset + DIRDATACHECKSUMOFFSET] = (0x0100 - transwarp_dirdata_checksum(image, entryOffset)); unsigned char dirdata_checksum = transwarp_dirdata_checksum(image, entryOffset); if (dirdata_checksum != 0) { if (dirdata_checksum == 1) { --image[entryOffset + DIRDATACHECKSUMOFFSET]; } dirdata_checksum = transwarp_dirdata_checksum(image, entryOffset); if (dirdata_checksum != 0) { fprintf(stderr, "ERROR: Encoding error with \"%s\", 0x%x\n", file->alocalname, dirdata_checksum); exit(-9); } } if (file->have_key != 0) { for (int offset = DIRDATACHECKSUMOFFSET; offset <= FILEBLOCKSLOOFFSET; ++offset) { image[entryOffset + offset] ^= key0; key0 >>= 8; } file->nrSectors = image[entryOffset + FILEBLOCKSLOOFFSET]; } } if (file->nrSectorsShown < 0) { file->nrSectorsShown = file->nrSectors; } image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectorsShown & 255; image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectorsShown >> 8; if (shadowdirtrack > 0) { entryOffset = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[entryOffset + FILETRACKOFFSET] = file->track; image[entryOffset + FILESECTOROFFSET] = file->sector; image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectors & 255; image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectors >> 8; } free(filedata); } } /* for each file */ /* Set track/sector of Transwarp file entries to Transwarp bootfile */ int transwarp_boot_track = 0; int transwarp_boot_sector = 0; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } for (int i = 0; i < num_files; i++) { imagefile *file = files + allocation_order[i]; if (file->filetype & FILETYPETRANSWARPMASK) { if (transwarp_boot_track == 0) { /* find Transwarp bootfile */ int t = dirtrack(type); int s = (type == IMAGE_D81) ? 3 : 1; int o = 0; do { int b = linear_sector(type, t, s) * BLOCKSIZE + o; int filetype = image[b + FILETYPEOFFSET] & 0xf; if (filetype == FILETYPEDEL) { continue; } if (is_transwarp_file(image, b)) { continue; } if (!is_transwarp_bootfile(image, b)) { continue; } int filetrack = image[b + FILETRACKOFFSET]; int filesector = image[b + FILESECTOROFFSET]; if (filetrack == 0) { continue; } if (verbose) { printf("\nTranswarp bootfile at T%d/S%d\n", filetrack, filesector); } transwarp_boot_track = filetrack; transwarp_boot_sector = filesector; break; } while (next_dir_entry(type, image, &t, &s, &o, blockmap)); } if (transwarp_boot_track == 0) { fprintf(stderr, "ERROR: No Transwarp bootfile provided\n"); exit(-10); } int b = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[b + FILETRACKOFFSET] = transwarp_boot_track; image[b + FILESECTOROFFSET] = transwarp_boot_sector; } } free(blockmap); /* update loop files */ for (int i = 0; i < num_files; i++) { imagefile *file = files + allocation_order[i]; if (((file->filetype & 0xf) != FILETYPEDEL) && (file->mode & MODE_LOOPFILE)) { int track, sector, offset; int index; if (find_existing_file(type, image, file->plocalname, &index, &track, §or, &offset)) { /* read track/sector and nrSectors from disk image */ int b = linear_sector(type, track, sector) * BLOCKSIZE + offset; file->track = image[b + FILETRACKOFFSET]; file->sector = image[b + FILESECTOROFFSET]; file->nrSectors = image[b + FILEBLOCKSLOOFFSET] + (image[b + FILEBLOCKSHIOFFSET] << 8); /* update directory entry */ b = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[b + FILETRACKOFFSET] = file->track; image[b + FILESECTOROFFSET] = file->sector; if (file->nrSectorsShown == -1) { file->nrSectorsShown = file->nrSectors; } image[b + FILEBLOCKSLOOFFSET] = file->nrSectorsShown & 255; image[b + FILEBLOCKSHIOFFSET] = file->nrSectorsShown >> 8; if (shadowdirtrack > 0) { b = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset; image[b + FILETRACKOFFSET] = file->track; image[b + FILESECTOROFFSET] = file->sector; image[b + FILEBLOCKSLOOFFSET] = file->nrSectors & 255; image[b + FILEBLOCKSHIOFFSET] = file->nrSectors >> 8; } for (int j = 0; j < num_files; j++) { imagefile *other_file = files + allocation_order[j]; if ((i != j) && (file->track == other_file->track) && (file->sector == other_file->sector)) { file->sectorInterleave = other_file->sectorInterleave; break; } } continue; } else { fprintf(stderr, "ERROR: Loop source file '%s' (%d) not found\n", file->alocalname, i + 1); exit(-1); } } } free(allocation_order); } /* Writes 16 bit value to file */ static size_t write16(unsigned int value, FILE* f) { char byte = value & 0xff; size_t bytes_written = fwrite(&byte, 1, 1, f); byte = (value >> 8) & 0xff; bytes_written += fwrite(&byte, 1, 1, f); return bytes_written; } /* Writes 32 bit value to file */ static size_t write32(unsigned int value, FILE* f) { size_t bytes_written = write16(value, f); bytes_written += write16(value >> 16, f); return bytes_written; } /* Writes image as G64 file */ static int generate_uniformat_g64(unsigned char* image, const char *imagepath) { FILE* f = fopen(imagepath, "wb"); size_t filepos = 0; static const char signature[] = "GCR-1541"; filepos += fwrite(signature, 1, sizeof signature - 1, f); const char version = 0; filepos += fwrite(&version, 1, 1, f); const char num_tracks = 35 * 2; filepos += fwrite(&num_tracks, 1, 1, f); const unsigned int track_size = 7692; /* = track_bytes on tracks 1..17 */ filepos += write16(track_size, f); const unsigned int table_size = num_tracks * 4; const unsigned int tracks_offset = (int)filepos + (table_size * 2); for (int track = 0; track < num_tracks; ++track) { unsigned int track_offset = 0; if ((track & 1) == 0) { track_offset = tracks_offset + ((track >> 1) * (2 + track_size)); } filepos += write32(track_offset, f); } for (int track = 0; track < num_tracks; ++track) { unsigned int bit_rate = 0; if ((track & 1) == 0) { switch (sectors_per_track[track >> 1]) { case 21: bit_rate = 3; break; case 19: bit_rate = 2; break; case 18: bit_rate = 1; break; case 17: bit_rate = 0; break; } } filepos += write32(bit_rate, f); } const unsigned char sync[] = { 0xff, 0xff, 0xff, 0xff, 0xff }; const char gap[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }; char header_gcr[10]; const unsigned int block_size = (sizeof sync) + (sizeof header_gcr) + (sizeof gap) + (sizeof sync) + 325; /* data */ const char id[3] = { '2', 'A', '\0' }; bool is_uniform = true; for (int track = 0; track < (num_tracks >> 1); ++track) { int track_bytes = 0; int num_sectors = sectors_per_track[track]; switch (num_sectors) { case 21: track_bytes = 7692; break; /* = track_size */ case 19: track_bytes = 7142; break; case 18: track_bytes = 6666; break; case 17: track_bytes = 6250; break; } filepos += write16(track_bytes, f); size_t track_begin = filepos; int data_bytes = num_sectors * block_size; int gap_size = (track_bytes - data_bytes) / num_sectors; if (gap_size < 0) { printf("\nERROR: Track too small for G64 output\n"); fclose(f); return -1; } float average_gap_remainder = (((float) (track_bytes - data_bytes)) / num_sectors) - gap_size; if (average_gap_remainder >= 1.0f) { average_gap_remainder = 0.0f; /* 0..1 */ } float remainder = 0.0f; for (int sector = 0; sector < num_sectors; ++sector) { unsigned int gap_bytes = gap_size; remainder += average_gap_remainder; if (remainder >= 0.5f) { remainder -= 1.0f; ++gap_bytes; } filepos += fwrite(sync, 1, sizeof sync, f); char header[8] = { 0x08, /* header ID */ (char) (sector ^ (track + 1) ^ id[1] ^ id[0]), /* checksum */ (char) sector, (char) (track + 1), id[1], id[0], 0x0f, 0x0f }; encode_4_bytes_gcr(header, header_gcr); encode_4_bytes_gcr(header + 4, header_gcr + 5); filepos += fwrite(header_gcr, 1, sizeof header_gcr, f); filepos += fwrite(gap, 1, sizeof gap, f); filepos += fwrite(sync, 1, sizeof sync, f); char group[5]; char checksum = image[0] ^ image[1] ^ image[2]; char data[4] = { 0x07, (char) image[0], (char) image[1], (char) image[2] }; encode_4_bytes_gcr(data, group); filepos += fwrite(group, 1, sizeof group, f); for (int i = 0; i < 0x3f; ++i) { data[0] = image[(i * 4) + 3]; data[1] = image[(i * 4) + 4]; data[2] = image[(i * 4) + 5]; data[3] = image[(i * 4) + 6]; encode_4_bytes_gcr(data, group); filepos += fwrite(group, 1, sizeof group, f); checksum ^= (data[0] ^ data[1] ^ data[2] ^ data[3]); } data[0] = image[0xff]; data[1] = data[0] ^ checksum; data[2] = 0; data[3] = 0; encode_4_bytes_gcr(data, group); filepos += fwrite(group, 1, sizeof group, f); for (int i = gap_bytes; i > 0; --i) { filepos += fwrite(gap, 1, 1, f); } image += 0x0100; } /* for each sector */ size_t tail_gap = track_bytes - filepos + track_begin; if (tail_gap > 0) { for (size_t i = tail_gap; i > 0; --i) { filepos += fwrite(gap, 1, 1, f); } is_uniform = false; } for (int i = (track_size - track_bytes); i > 0; --i) { filepos += fwrite(sync, 1, 1, f); } } /* for each track */ fclose(f); if (!is_uniform) { printf("\nWARNING: \"%s\" is not UniFormAt'ed\n", imagepath); } return 0; } /* Generates a unique filename, either based on the proposed name, or using track and sector. */ static void generate_unique_filename(image_type type, unsigned char *image, unsigned char *name, int track, int sector, int start, char marker) { int i, t, s, o; if(name[0] == 0xa0 || name[0] == 0) { /* no name provided, create one */ name[0] = a2p('t'); pputnum2(name+1, track); name[3] = a2p('s'); pputnum2(name+4, sector); name[6] = a2p('$'); pputhex(name+7, start); for(int pos = 11; pos < FILENAMEMAXSIZE; pos++) { name[pos] = 0xa0; } } int marker_pos = pstrlen(name); if(marker_pos > FILENAMEMAXSIZE-1) { marker_pos = FILENAMEMAXSIZE-1; } if(marker) { name[marker_pos] = marker; } else { marker_pos++; } int appendix = 1; int appendix_len = 2; int namelen = pstrlen(name); while(find_existing_file(type, image, name, &i, &t, &s, &o)) { marker_pos = namelen + appendix_len; if(marker_pos > FILENAMEMAXSIZE-1) { marker_pos = FILENAMEMAXSIZE-1; if(!marker) { marker_pos++; } } if(marker) { name[marker_pos] = marker; } int appendix_pos = marker_pos - appendix_len; name[appendix_pos] = '.'; pputnum(name + appendix_pos + 1, appendix); appendix++; if(appendix == 10) { appendix_len++; } if(appendix == 100) { appendix_len++; } } } /* Count number of blocks in file, assumes valid t/s chain */ static int count_blocks(image_type type, unsigned char *image, unsigned int track, int sector) { int num = 0; while(track != 0) { int b = linear_sector(type, track, sector); if(b < 0) { break; } int block_offset = b * BLOCKSIZE; track = image[block_offset + TRACKLINKOFFSET]; sector = image[block_offset + SECTORLINKOFFSET]; num++; }; return num; } /* Overwrites all blocks that are marked as potentially allocated with the given value */ static void mark_sector_chain(image_type type, unsigned char *image, char* atab, unsigned int track, int sector, unsigned int last_track, int last_sector, int mark) { if(track <= 0 || track > image_num_tracks(type) || sector < 0 || sector > num_sectors(type, track)) { return; } while(1) { int b = linear_sector(type, track, sector); atab[b] = mark; if(track == last_track && sector == last_sector) { break; } int block_offset = b * BLOCKSIZE; track = image[block_offset + TRACKLINKOFFSET]; sector = image[block_offset + SECTORLINKOFFSET]; }; } /* Validates sector chain starting at track/sector, returns how and in which t/s the chain ends */ static int validate_sector_chain(image_type type, unsigned char* image, char* atab, unsigned int track, int sector, unsigned int *last_track, int *last_sector) { *last_track = track; *last_sector = sector; if(track == 0 || track > image_num_tracks(type)) { return FIRST_BROKEN; } if(sector >= num_sectors(type, track)) { return FIRST_BROKEN; } if(atab[linear_sector(type, track, sector)] != UNALLOCATED) { return FIRST_BROKEN; } while (1) { atab[linear_sector(type, track, sector)] = POTENTIALLYALLOCATED; int block_offset = linear_sector(type, track, sector) * BLOCKSIZE; unsigned int next_track = image[block_offset + TRACKLINKOFFSET]; int next_sector = image[block_offset + SECTORLINKOFFSET]; if (next_track == 0) { return VALID; /* end of valid chain */ } if (next_track > image_num_tracks(type)) { return ILLEGAL_TRACK; } if (next_sector >= num_sectors(type, next_track)) { return ILLEGAL_SECTOR; } switch(atab[linear_sector(type, next_track, next_sector)]) { case POTENTIALLYALLOCATED: return LOOP; case ALLOCATED: return COLLISION; case FILESTART: return CHAINED; case FILESTART_TRUNCATED: return CHAINED_TRUNCATED; } track = next_track; sector = next_sector; *last_track = track; *last_sector = sector; } } /* Allocates and initialises a table to track block allocation */ static void init_atab(image_type type, unsigned char* image, char* atab) { int dt = dirtrack(type); int ds = (type == IMAGE_D81) ? 3 : 1; int offset = 0; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } do { int db = linear_sector(type, dt, ds); atab[db] = ALLOCATED; int filetype = image[db * BLOCKSIZE + offset + FILETYPEOFFSET] & 0xf; if(filetype != FILETYPEDEL) { int track = image[db * BLOCKSIZE + offset + FILETRACKOFFSET]; int sector = image[db * BLOCKSIZE + offset + FILESECTOROFFSET]; unsigned int last_track; int last_sector; int error = validate_sector_chain(type, image, atab, track, sector, &last_track, &last_sector); if(error != VALID) { printf("WARNING: file "); print_filename(stdout, &image[db * BLOCKSIZE + offset + FILENAMEOFFSET]); printf(" seems corrupt (%s)\n", error_name[error]); } mark_sector_chain(type, image, atab, track, sector, last_track, last_sector, ALLOCATED); } } while(next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); } /* Write atab into BAM */ static void write_atab(image_type type, unsigned char* image, char* atab) { for(unsigned int t = 1; t <= image_num_tracks(type); t++) { for(int s = 0; s < num_sectors(type, t); s++) { int b = linear_sector(type, t, s); int free = (atab[b] == UNALLOCATED); if(!free) { /* keep allocated BAM entries, only add new ones */ mark_sector(type, image, t, s, 0); } } } } /* Try to undelete a file given the directory entry, returns true if successful */ static bool undelete_file(image_type type, unsigned char* image, int dt, int ds, int offset, char* atab, int level) { unsigned int last_track; int last_sector; char marker = 0; int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE; int track = image[dirblock + offset + FILETRACKOFFSET]; int sector = image[dirblock + offset + FILESECTOROFFSET]; int error = validate_sector_chain(type, image, atab, track, sector, &last_track, &last_sector); if(error == VALID || ((level == RESTORE_DIR_ONLY || level >= RESTORE_INVALID_FILES) && error != FIRST_BROKEN)) { unsigned char name[17]; if(error != VALID) { if(level != RESTORE_DIR_ONLY) { /* terminate chain */ int block_offset = linear_sector(type, last_track, last_sector) * BLOCKSIZE; image[block_offset + TRACKLINKOFFSET] = 0; marker = '<'; } } /* restore dir entry */ memcpy(&name, &image[dirblock + offset + FILENAMEOFFSET], 16); name[16] = 0; int b = linear_sector(type, track, sector); int address = image[b * BLOCKSIZE + 2] + 256 * image[b * BLOCKSIZE + 3]; generate_unique_filename(type, image, name, track, sector, address, marker); memcpy(&image[dirblock + offset + FILENAMEOFFSET], &name, 16); image[dirblock + offset + FILETYPEOFFSET] = 0x82; /* original file type is lost, use closed PRG instead */ mark_sector_chain(type, image, atab, track, sector, last_track, last_sector, ALLOCATED); if(level != RESTORE_DIR_ONLY) { int size = count_blocks(type, image, track, sector); image[dirblock + offset + FILEBLOCKSHIOFFSET] = size / 256; image[dirblock + offset + FILEBLOCKSLOOFFSET] = size % 256; } return true; } if(error != FIRST_BROKEN) { mark_sector_chain(type, image, atab, track, sector, last_track, last_sector, UNALLOCATED); } return false; } /* search for scratched directory entries and restore them */ static int undelete(image_type type, unsigned char* image, char* atab, int level) { int dt = dirtrack(type); int ds = (type == IMAGE_D81) ? 3 : 1; int nsectors = num_sectors(type, dt); int offset = 0; int num_undeleted = 0; int final_dt, final_ds; /* last linked directory sector and track */ char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } bool* searched = calloc(nsectors, sizeof(bool)); if(searched == NULL) { fprintf(stderr, "ERROR: error allocating memory"); exit(-1); } /* go through directory sector chain */ do { if(dt == dirtrack(type)) { searched[ds] = true; } final_dt = dt; final_ds = ds; int db = linear_sector(type, dt, ds); mark_sector(type, image, dt, ds, 0); /* needs to be updated in BAM, so that dir allocation works correctly later */ atab[db] = ALLOCATED; int dirblock = db * BLOCKSIZE; int filetype = image[dirblock + offset + FILETYPEOFFSET]; if(filetype == FILETYPEDEL && image[dirblock + offset + FILENAMEOFFSET] != 0 && undelete_file(type, image, dt, ds, offset, atab, level)) { /* filename starting with 0 means that likely there was no file */ num_undeleted++; } } while(next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); /* search for unlinked dir sectors */ dt = dirtrack(type); for(ds = (type == IMAGE_D81) ? 3 : 1; ds < nsectors; ds++) { if(!searched[ds]) { int found = 1; for(offset = 0; offset < DIRENTRIESPERBLOCK*DIRENTRYSIZE; offset += DIRENTRYSIZE) { if(!undelete_file(type, image, dt, ds, offset, atab, level)) { found = 0; break; } } if(found) { num_undeleted += 8; if(!quiet) { printf("Relinking directory sector %d\n", ds); } /* link found dir sector */ int block_offset = linear_sector(type, final_dt, final_ds) * BLOCKSIZE; image[block_offset + TRACKLINKOFFSET] = dt; image[block_offset + SECTORLINKOFFSET] = ds; /* terminate found dir sector */ int db = linear_sector(type, dt, ds); block_offset = db * BLOCKSIZE; image[block_offset + TRACKLINKOFFSET] = 0; final_dt = dt; final_ds = ds; mark_sector(type, image, dt, ds, 0); /* needs to be updated in BAM, so that dir allocation works correctly later */ atab[db] = ALLOCATED; } } } free(searched); write_atab(type, image, atab); return num_undeleted; } /* add new DIR entries for wild chains */ static void add_wild_to_dir(image_type type, unsigned char* image, char* atab, imagefile files[]) { /* create a DIR entry for each FILESTART */ for(unsigned int t = 1; t <= image_num_tracks(type); t++) { for(int s = 0; s < num_sectors(type, t); s++) { int b = linear_sector(type, t, s); if(atab[b] == FILESTART || atab[b] == FILESTART_TRUNCATED) { char marker = atab[b] == FILESTART ? 0 : '<'; unsigned char name[17]; name[0] = 0xa0; int dir_index, dir_sector, dir_offset; atab[b] = ALLOCATED; new_dir_slot(type, image, (type == IMAGE_D81 ? 1 : 3), 0, &dir_index, &dir_sector, &dir_offset, files); /* TODO: handle full directory more gracefully */ int db = linear_sector(type, dirtrack(type), dir_sector); atab[db] = ALLOCATED; /* make sure that potentially new dir block is marked as used */ int offset = db * BLOCKSIZE + dir_offset; image[offset + FILETYPEOFFSET] = 0x82; /* closed PRG */ image[offset + FILETRACKOFFSET] = t; image[offset + FILESECTOROFFSET] = s; image[offset + FILENAMEOFFSET] = 0xa0; /* no proposed filename */ int address = image[b * BLOCKSIZE + 2] + 256 * image[b * BLOCKSIZE + 3]; generate_unique_filename(type, image, name, t, s, address, marker); memcpy(&image[offset + FILENAMEOFFSET], name, 16); int size = count_blocks(type, image, t, s); image[offset + FILEBLOCKSHIOFFSET] = size / 256; image[offset + FILEBLOCKSLOOFFSET] = size % 256; } } } } /* search for wild valid chains of unallocated sectors */ static int undelete_wild(image_type type, unsigned char* image, char* atab, int level, imagefile files[]) { int num_undeleted = 0; int max_bam_sector = (type == IMAGE_D81) ? 2 : 0; unsigned int dt = dirtrack(type); /* search for well terminated sector chains */ for(unsigned int t = 1; t <= image_num_tracks(type); t++) { for(int s = 0; s < num_sectors(type, t); s++) { int b = linear_sector(type, t, s); if(atab[b] == UNALLOCATED && (t != dt || s > max_bam_sector)) { /* ignore bam sectors */ unsigned int last_track; int last_sector; int error = validate_sector_chain(type, image, atab, t, s, &last_track, &last_sector); int last_block = linear_sector(type, last_track, last_sector); if(error == CHAINED || error == CHAINED_TRUNCATED) { mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED); atab[b] = (error == CHAINED) ? FILESTART : CHAINED_TRUNCATED; int chained_track = image[last_block * BLOCKSIZE + TRACKLINKOFFSET]; int chained_sector = image[last_block * BLOCKSIZE + SECTORLINKOFFSET]; int chained_block = linear_sector(type, chained_track, chained_sector); atab[chained_block] = ALLOCATED; /* overwrite FILESTART */ } else if(error == VALID) { if(last_track == t && last_sector == s) { if(level == RESTORE_INVALID_SINGLES) { /* single block files should have all 0 after file end */ bool valid = false; int last_byte = image[last_block * BLOCKSIZE + SECTORLINKOFFSET]; if(image[last_block * BLOCKSIZE + TRACKLINKOFFSET] == 0 && last_byte >= 2) { valid = true; for(int i = last_byte + 1; i <= 255; i++) { if(image[last_block * BLOCKSIZE + i] != 0) { valid = false; break; } } } if(valid) { atab[b] = FILESTART; num_undeleted++; } else { atab[b] = UNALLOCATED; } atab[b] = valid ? FILESTART : UNALLOCATED; } else { atab[b] = UNALLOCATED; } } else { mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED); atab[b] = FILESTART; image[last_block * BLOCKSIZE + TRACKLINKOFFSET] = 0; num_undeleted++; } } else { if(error != FIRST_BROKEN) { mark_sector_chain(type, image, atab, t, s, last_track, last_sector, UNALLOCATED); } } } } } if(num_undeleted) { write_atab(type, image, atab); add_wild_to_dir(type, image, atab, files); } return num_undeleted; } /* search for wild invalid chains of unallocated sectors and fix them */ static int undelete_fix_wild(image_type type, unsigned char* image, char* atab, imagefile files[]) { int num_undeleted = 0; int max_bam_sector = (type == IMAGE_D81) ? 2 : 0; unsigned int dt = dirtrack(type); /* search for broken sector chains */ for(unsigned int t = 1; t <= image_num_tracks(type); t++) { for(int s = 0; s < num_sectors(type, t); s++) { int b = linear_sector(type, t, s); if(atab[b] == UNALLOCATED && (t != dt || s > max_bam_sector)) { /* ignore bam sectors */ unsigned int last_track; int last_sector; int error = validate_sector_chain(type, image, atab, t, s, &last_track, &last_sector); int last_block = linear_sector(type, last_track, last_sector); if(error == CHAINED || error == CHAINED_TRUNCATED) { mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED); atab[b] = (error == CHAINED) ? FILESTART : FILESTART_TRUNCATED; int chained_track = image[last_block * BLOCKSIZE + TRACKLINKOFFSET]; int chained_sector = image[last_block * BLOCKSIZE + SECTORLINKOFFSET]; int chained_block = linear_sector(type, chained_track, chained_sector); atab[chained_block] = ALLOCATED; /* overwrite FILESTART */ } else if(t != last_track || s != last_sector) { mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED); atab[b] = FILESTART_TRUNCATED; image[last_block * BLOCKSIZE + TRACKLINKOFFSET] = 0; image[last_block * BLOCKSIZE + SECTORLINKOFFSET] = 255; num_undeleted++; } else { if(error != FIRST_BROKEN) { mark_sector_chain(type, image, atab, t, s, last_track, last_sector, UNALLOCATED); } } } } } if(num_undeleted) { write_atab(type, image, atab); add_wild_to_dir(type, image, atab, files); } return num_undeleted; } /* Tries to restore any deleted or formatted files */ static void restore(image_type type, unsigned char* image, int level, imagefile files[]) { int num_undeleted = 0; /* create block allocation table */ char *atab = (char *)calloc(image_num_blocks(type), sizeof(char)); if (atab == NULL) { fprintf(stderr, "ERROR: error allocating memory"); exit(-1); } init_atab(type, image, atab); if(level == RESTORE_DIR_ONLY) { num_undeleted += undelete(type, image, atab, RESTORE_DIR_ONLY); } else { num_undeleted += undelete(type, image, atab, RESTORE_VALID_FILES); if(level >= RESTORE_VALID_CHAINS) { num_undeleted += undelete_wild(type, image, atab, RESTORE_VALID_CHAINS, files); } if(level >= RESTORE_INVALID_FILES) { num_undeleted += undelete(type, image, atab, RESTORE_INVALID_FILES); } if(level >= RESTORE_INVALID_CHAINS) { num_undeleted += undelete_fix_wild(type, image, atab, files); } if(level >= RESTORE_INVALID_SINGLES) { num_undeleted += undelete_wild(type, image, atab, RESTORE_INVALID_SINGLES, files); } } free(atab); if(num_undeleted) { modified = 1; } if(!quiet) { printf("%d files undeleted", num_undeleted); if(num_undeleted) { printf(", '<' at filename end marks truncated files"); } printf("\n"); } } /* Prints a command line to create dir art like the given image */ static void convert_to_commandline(image_type type, unsigned char* image) { char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } printf("\nCommandline to create directory art: -n \""); unsigned int bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE; print_filename_with_escapes(image + bam + get_header_offset(type), FILENAMEMAXSIZE); printf("\" -i \""); print_filename_with_escapes(image + bam + get_id_offset(type), 5); printf("\" "); int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET]; if (filetype) { if(filetype != 0x82) { printf("-T %d ", filetype); } int size = image[dirblock + FILEBLOCKSLOOFFSET] + 256 * image[dirblock + FILEBLOCKSHIOFFSET]; if(size != 0) { printf("-B %d ", size); } unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET; printf("-N -f \""); print_filename_with_escapes(filename, FILENAMEMAXSIZE); printf("\" -L "); } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); free(blockmap); printf("\n\n"); } /* Performs strict CBM DOS validation on the image */ static void validate(image_type type, unsigned char* image) { /* create block allocation table */ char *atab = (char *)calloc(image_num_blocks(type), sizeof(int)); if (atab == NULL) { fprintf(stderr, "ERROR: error allocating memory"); exit(-1); } /* check format specifier */ int format = image[linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + 2]; if ((type == IMAGE_D64 || type == IMAGE_D71) && format != 0x41) { fprintf(stderr, "ERROR: validation failed, format specifier in directory (0x%02x) does not specify 1541/1570/1571 (0x41)\n", format); exit(-1); } else if(type == IMAGE_D81 && format != 0x44) { fprintf(stderr, "ERROR: validation failed, format specifier in directory (0x%02x) does not specify 1581 (0x44)\n", format); exit(-1); } /* check double sided flag */ int doublesided = image[linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + 3]; if (type == IMAGE_D64 && doublesided != 0) { fprintf(stderr, "ERROR: validation failed, double sided flag in directory not zero (0x%02x) for D64\n", doublesided); exit(-1); } if (type == IMAGE_D71 && doublesided != 0x80) { fprintf(stderr, "ERROR: validation failed, double sided flag in directory not 0x80 (0x%02x) for D71\n", doublesided); exit(-1); } /* check each directory entry and set block allocation table */ /* set BAM sector as allocated */ atab[linear_sector(type, dirtrack(type), 0)] = ALLOCATED; if (type == IMAGE_D71) { /* mark full track of second BAM as allocated */ for (int s = 0; s < num_sectors(type, dirtrack(type) + D64NUMTRACKS); s++) { atab[linear_sector(type, dirtrack(type) + D64NUMTRACKS, s)] = ALLOCATED; } } if(type == IMAGE_D81) { /* mark additional BAM sectors as allocated */ atab[linear_sector(type, dirtrack(type), 1)] = ALLOCATED; atab[linear_sector(type, dirtrack(type), 2)] = ALLOCATED; } unsigned int dt = dirtrack(type); int dirsector = (type == IMAGE_D81) ? 3 : 1; unsigned int start_track = 1; while (start_track != 0) { atab[linear_sector(type, dt, dirsector)] = ALLOCATED; int dirblock = linear_sector(type, dt, dirsector) * BLOCKSIZE; for (int direntry = 0; direntry < DIRENTRIESPERBLOCK; direntry++) { int entryOffset = direntry * DIRENTRYSIZE; int filetype = image[dirblock + entryOffset + FILETYPEOFFSET] & 0xf; if (filetype > 4) { fprintf(stderr, "ERROR: validation failed, illegal file type (0x%02x) in directory\n", filetype); exit(-1); } if (filetype != 0) { /* skip deleted entries */ start_track = image[dirblock + entryOffset + FILETRACKOFFSET]; int start_sector = image[dirblock + entryOffset + FILESECTOROFFSET]; if (start_track == 0 || start_track > image_num_tracks(type)) { fprintf(stderr, "ERROR: validation failed, illegal track reference (%u) in directory\n", start_track); exit(-1); } if (start_sector >= num_sectors(type, start_track)) { fprintf(stderr, "ERROR: validation failed, illegal sector reference (track %u, sector %d) in directory\n", start_track, start_sector); exit(-1); } if (atab[linear_sector(type, start_track, start_sector)] == ALLOCATED) { fprintf(stderr, "ERROR: validation failed, file starts in the middle of another file (track %u, sector %d)\n", start_track, start_sector); exit(-1); } if (atab[linear_sector(type, start_track, start_sector)] != FILESTART) { /* loop files are allowed */ unsigned int error_track; int error_sector; int result = validate_sector_chain(type, image, atab, start_track, start_sector, &error_track, &error_sector); switch(result) { case ILLEGAL_TRACK: fprintf(stderr, "ERROR: validation failed, illegal track reference in file sector chain at track %d, sector %d\n", error_track, error_sector); exit(-1); case ILLEGAL_SECTOR: fprintf(stderr, "ERROR: validation failed, illegal sector reference in file sector chain at track %d, sector %d\n", error_track, error_sector); exit(-1); case LOOP: fprintf(stderr, "ERROR: validation failed, loop in file sector chain at track %d, sector %d\n", error_track, error_sector); exit(-1); case COLLISION: case CHAINED: fprintf(stderr, "ERROR: validation failed, collision with existing file in file sector chain at track %d, sector %d\n", error_track, error_sector); exit(-1); } atab[linear_sector(type, start_track, start_sector)] = FILESTART; mark_sector_chain(type, image, atab, start_track, start_sector, error_track, error_sector, ALLOCATED); } } } dt = image[dirblock + TRACKLINKOFFSET]; dirsector = image[dirblock + SECTORLINKOFFSET]; if (dt == 0) { break; } } /* check BAM for consistency with block allocation table */ for (unsigned int t = 1; t <= image_num_tracks(type); t++) { unsigned char* bitmap = image + get_bam_offset(type, t); int num_free = 0; int exp_free; for (int s = 0; s < num_sectors(type, t); s++) { int atab_used = (atab[linear_sector(type, t, s)] != UNALLOCATED); int bam_used = ((bitmap[s >> 3] & (1 << (s & 7))) == 0); num_free += (1 - bam_used); if (bam_used != atab_used) { fprintf(stderr, "ERROR: validation failed, BAM (%s) is not consistent with files (%s) for track %u sector %d\n", bam_used ? "used" : "free", atab_used ? "used" : "free", t, s); exit(-1); } } if (type != IMAGE_D71 || t <= 35) { exp_free = *(bitmap - 1); } else { exp_free = image[linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + 0xdd + t - D64NUMTRACKS - 1]; } if (exp_free != num_free) { fprintf(stderr, "ERROR: validation failed, BAM number of free blocks (%d) is not consistent with bitmap (%#02x%#02x%#02x) for track %u\n", exp_free, *bitmap, *(bitmap + 1), *(bitmap + 2), t); exit(-1); } } free(atab); if(!quiet) { fprintf(stderr, "CBM DOS validation passed\n"); } } /* Apply list of binary patches to image */ static void apply_patches(image_type type, unsigned char* image, patch patches[], int num_patches) { for(int p = 0; p < num_patches; p++) { unsigned int t = patches[p].track; int s = patches[p].sector; int o = patches[p].offset; const char *n = patches[p].filename; if(t < 1 || t >= image_num_tracks(type)) { fprintf(stderr, "ERROR: Illegal track for -p\n"); exit(-1); } if(s < 0 || s >= num_sectors(type, t)) { fprintf(stderr, "ERROR: Illegal sector for -p\n"); exit(-1); } if(o < 0 || o > 255) { fprintf(stderr, "ERROR: Illegal offset for -p\n"); exit(-1); } int pos = linear_sector(type, t, s) * BLOCKSIZE + o; int maxread = image_size(type) - pos; FILE* f = fopen(n, "rb"); if (f == NULL) { fprintf(stderr, "ERROR: Could not open patch file \"%s\" for reading\n", n); exit(-1); } int numread = fread(image + pos, 1, maxread, f); if(!feof(f)) { fprintf(stderr, "ERROR: Patch file \"%s\" overflows disk\n", n); exit(-1); } fclose(f); while(numread + o > 0) { mark_sector(type, image, t, s++, 0); if(s >= num_sectors(type, t)) { s = 0; t++; } numread -= BLOCKSIZE; } } } /* Extract files according to patterns */ static void extract_files(image_type type, unsigned char* image, char *patterns[], int num_patterns) { bool found = false; char *blockmap = calloc(image_num_blocks(type), sizeof(char)); if(blockmap == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); exit(-1); } int ds = (type == IMAGE_D81) ? 3 : 1; int dt = dirtrack(type); int offset = 0; do { int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset; int filetype = image[dirblock + FILETYPEOFFSET]; if (filetype != FILETYPEDEL) { unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET; for(int p = 0; p < num_patterns; p++) { unsigned char petscii[FILENAMEMAXSIZE]; evalhexescape((unsigned char*)patterns[p], petscii, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR); if(match_filename(petscii, filename)) { char afilename[3 * FILENAMEMAXSIZE + 4 + 1]; /* space for hexevals, extension and terminating 0 */ found = true; petscii2ascii(filename, afilename, FILENAMEMAXSIZE, false); int pext = strlen(afilename); filetype &= 7; if(filetype <= 5) { afilename[pext] = '.'; strcpy(afilename + pext + 1, filetypename_lc[filetype]); } unsigned int track = *(image + dirblock + FILETRACKOFFSET); int sector = *(image + dirblock + FILESECTOROFFSET); unsigned int last_track; int last_sector; int result = validate_sector_chain(type, image, blockmap, track, sector, &last_track, &last_sector); printf("\nExtracting %s", afilename); if(result == FIRST_BROKEN) { printf(", ERROR: directory entry invalid"); } else { if(result != VALID) { printf(", WARNING: file corrupt"); } FILE *file = fopen(afilename, "wb"); if(file == NULL) { fprintf(stderr, "\nERROR: Could not open output file %s\n", afilename); exit(-1); } while(track != last_track || sector != last_sector) { unsigned char *block = image + linear_sector(type, track, sector) * BLOCKSIZE; fwrite(block + 2, 1, 254, file); track = *block; sector = *(block + 1); } unsigned char *block = image + linear_sector(type, track, sector) * BLOCKSIZE; fwrite(block + 2, 1, *(block + 1) - 1, file); fclose(file); } break; } } } } while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap)); if(found) { printf("\n"); } free(blockmap); } int main(int argc, char* argv[]) { imagefile files[MAXNUMFILES_D81]; memset(files, 0, sizeof files); patch patches[MAXNUMPATCHES]; memset(patches, 0, sizeof patches); int num_patches = 0; char *extractions[MAXNUMEXTRACTIONS]; memset(extractions, 0, sizeof extractions); int num_extractions = 0; image_type type = IMAGE_D64; char* imagepath = NULL; char* filename_g64 = NULL; unsigned char* header = (unsigned char*)"cc1541"; unsigned char* id = (unsigned char*)"00#a02a"; unsigned char* bam_message = NULL; int dirtracksplit = 1; int usedirtrack = 0; unsigned int shadowdirtrack = 0; int default_first_sector_new_track = 0; int first_sector_new_track = 0; int defaultSectorInterleave = 10; int sectorInterleave = 0; int dir_sector_interleave = 3; int numdirblocks = 2; int nrSectorsShown = -1; unsigned char* filename = NULL; int set_header = 0; int nooverwrite = 0; int dovalidate = 0; int restore_level = -1; int ignore_collision = 0; int filetype = 0x82; /* default is closed PRG */ bool filetype_set = false; bool print_art_commandline = false; int allocation_strategy = 1; /* flags to detect illegal settings */ int transwarp_set = 0; int sector_interleave_set = 0; int default_sector_interleave_set = 0; int file_start_sector_set = 0; int new_track_start_sector_set = 0; int cluster_optimized = 0; int retval = 0; int i, j; if (argc == 1 || strcmp(argv[argc-1], "-h") == 0) { usage(); } /* read environment variable for unicode printout */ char *unicode_env = getenv("CC1541UNICODE"); if(unicode_env != NULL) { unicode = atoi(unicode_env); if(unicode < 0 || unicode > 2) { unicode = 0; } } for (j = 1; j < argc - 1; j++) { if (strcmp(argv[j], "-n") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -n\n"); return -1; } header = (unsigned char*)argv[++j]; set_header = 1; modified = 1; } else if (strcmp(argv[j], "-i") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -i\n"); return -1; } id = (unsigned char*)argv[++j]; set_header = 1; modified = 1; } else if (strcmp(argv[j], "-H") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -H\n"); return -1; } bam_message = (unsigned char*)argv[++j]; set_header = 1; modified = 1; } else if (strcmp(argv[j], "-M") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &max_hash_length)) { fprintf(stderr, "ERROR: Error parsing argument for -M\n"); return -1; } if ((max_hash_length < 1) || (max_hash_length > FILENAMEMAXSIZE)) { fprintf(stderr, "ERROR: Hash computation maximum filename length %d specified\n", max_hash_length); return -1; } } else if (strcmp(argv[j], "-m") == 0) { ignore_collision = 1; } else if (strcmp(argv[j], "-F") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &first_sector_new_track)) { fprintf(stderr, "ERROR: Error parsing argument for -F\n"); return -1; } new_track_start_sector_set = 1; } else if (strcmp(argv[j], "-S") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &defaultSectorInterleave)) { fprintf(stderr, "ERROR: Error parsing argument for -S\n"); return -1; } if(defaultSectorInterleave < 1 || defaultSectorInterleave > 21) { fprintf(stderr, "ERROR: Illegal value for -S\n"); return -1; } default_sector_interleave_set = 1; } else if (strcmp(argv[j], "-s") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", §orInterleave)) { fprintf(stderr, "ERROR: Error parsing argument for -s\n"); return -1; } if(sectorInterleave < 1 || sectorInterleave > 21) { fprintf(stderr, "ERROR: Illegal value for -s\n"); return -1; } sector_interleave_set = 1; } else if (strcmp(argv[j], "-f") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -f\n"); return -1; } filename = (unsigned char*)argv[++j]; } else if (strcmp(argv[j], "-A") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &allocation_strategy)) { fprintf(stderr, "ERROR: Error parsing argument for -A\n"); return -1; } if(allocation_strategy < 0 || allocation_strategy > 1) { fprintf(stderr, "ERROR: Illegal value for -A\n"); return -1; } } else if (strcmp(argv[j], "-e") == 0) { files[num_files].mode |= MODE_SAVETOEMPTYTRACKS; } else if (strcmp(argv[j], "-E") == 0) { files[num_files].mode |= MODE_FITONSINGLETRACK; } else if (strcmp(argv[j], "-r") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &i)) { fprintf(stderr, "ERROR: Error parsing argument for -r\n"); return -1; } if ((i < 1) || (((i << MODE_MIN_TRACK_SHIFT) & MODE_MIN_TRACK_MASK) != (i << MODE_MIN_TRACK_SHIFT))) { fprintf(stderr, "ERROR: Invalid minimum track %d specified\n", i); return -1; } files[num_files].mode = (files[num_files].mode & ~MODE_MIN_TRACK_MASK) | (i << MODE_MIN_TRACK_SHIFT); } else if (strcmp(argv[j], "-b") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &i)) { fprintf(stderr, "ERROR: Error parsing argument for -b\n"); return -1; } if ((i < 0) || (i >= num_sectors(IMAGE_D64, 1))) { /* same for all applicable images, so simply use D64 */ fprintf(stderr, "ERROR: Invalid beginning sector %d specified\n", i); return -1; } files[num_files].mode = (files[num_files].mode & ~MODE_BEGINNING_SECTOR_MASK) | (i + 1); file_start_sector_set = 1; } else if (strcmp(argv[j], "-c") == 0) { files[num_files].mode |= MODE_SAVECLUSTEROPTIMIZED; cluster_optimized = 1; } else if (strcmp(argv[j], "-o") == 0) { nooverwrite = 1; } else if (strcmp(argv[j], "-V") == 0) { dovalidate = 1; } else if (strcmp(argv[j], "-T") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -T\n"); return -1; } if (strcmp(argv[j + 1], "DEL") == 0) { filetype = (filetype & 0xf0) | FILETYPEDEL; } else if (strcmp(argv[j + 1], "SEQ") == 0) { filetype = (filetype & 0xf0) | FILETYPESEQ; } else if (strcmp(argv[j + 1], "PRG") == 0) { filetype = (filetype & 0xf0) | FILETYPEPRG; } else if (strcmp(argv[j + 1], "USR") == 0) { filetype = (filetype & 0xf0) | FILETYPEUSR; } else if (strcmp(argv[j + 1], "REL") == 0) { filetype = (filetype & 0xf0) | FILETYPEREL; } else { char* dummy; filetype = strtol(argv[j + 1], &dummy, 10); if(dummy == argv[j + 1] || *dummy != 0 || filetype < 0 || filetype > 255) { fprintf(stderr, "ERROR: Error parsing argument for -T\n"); return -1; } } filetype_set = true; j++; } else if (strcmp(argv[j], "-O") == 0) { filetype &= 0x7f; } else if (strcmp(argv[j], "-P") == 0) { filetype |= 0x40; } else if (strcmp(argv[j], "-N") == 0) { files[num_files].force_new = 1; ignore_collision = 1; } else if (strcmp(argv[j], "-K") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -K\n"); return -1; } evalhexescape((unsigned char *) argv[++j], files[num_files].key, TRANSWARPKEYSIZE, 0); files[num_files].have_key = true; } else if ((strcmp(argv[j], "-w") == 0) || (strcmp(argv[j], "-W") == 0)) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for %s\n", argv[j]); return -1; } files[num_files].alocalname = (unsigned char*)argv[j + 1]; if (filename == NULL) { ascii2petscii(basename(files[num_files].alocalname), files[num_files].pfilename, FILENAMEMAXSIZE); /* do not eval escapes when converting the filename, as the local filename could contain the escape char */ } else { evalhexescape(filename, files[num_files].pfilename, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR); } files[num_files].sectorInterleave = sectorInterleave ? sectorInterleave : defaultSectorInterleave; files[num_files].first_sector_new_track = first_sector_new_track; files[num_files].nrSectorsShown = nrSectorsShown; files[num_files].filetype = filetype; files[num_files].direntryindex = -1; if (strcmp(argv[j], "-W") == 0) { if(nrSectorsShown != -1) { fprintf(stderr, "ERROR: -B cannot be used for Transwarp files\n"); return -1; } if(file_start_sector_set) { fprintf(stderr, "ERROR: -b cannot be used for Transwarp files\n"); return -1; } if(sector_interleave_set) { fprintf(stderr, "ERROR: -s cannot be used for Transwarp files\n"); return -1; } transwarp_set = true; if(!filetype_set && files[num_files].have_key) { files[num_files].filetype = (filetype & 0xf0) | FILETYPEUSR | FILETYPETRANSWARPMASK; } else { files[num_files].filetype = filetype | FILETYPETRANSWARPMASK; } files[num_files].sectorInterleave = 1; } first_sector_new_track = default_first_sector_new_track; filename = NULL; sectorInterleave = 0; nrSectorsShown = -1; filetype = 0x82; filetype_set = false; num_files++; modified = 1; j++; } else if (strcmp(argv[j], "-l") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -l\n"); return -1; } files[num_files].alocalname = (unsigned char*)argv[j + 1]; evalhexescape(files[num_files].alocalname, files[num_files].plocalname, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR); if (filename == NULL) { fprintf(stderr, "ERROR: Loop files require a filename set with -f\n"); return -1; } evalhexescape(filename, files[num_files].pfilename, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR); if(memcmp(files[num_files].pfilename, files[num_files].plocalname, FILENAMEMAXSIZE) == 0 && !files[num_files].force_new) { fprintf(stderr, "ERROR: Loop file cannot have the same name as the file they refer to, unless with -N\n"); return -1; } files[num_files].mode |= MODE_LOOPFILE; files[num_files].sectorInterleave = 0; files[num_files].first_sector_new_track = first_sector_new_track; first_sector_new_track = default_first_sector_new_track; files[num_files].nrSectorsShown = nrSectorsShown; files[num_files].filetype = filetype; files[num_files].direntryindex = -1; filename = NULL; sectorInterleave = 0; nrSectorsShown = -1; filetype = 0x82; filetype_set = false; num_files++; modified = 1; j++; } else if (strcmp(argv[j], "-L") == 0) { if (filename == NULL) { fprintf(stderr, "ERROR: Writing no file using -L requires disk filename set with -f\n"); return -1; } evalhexescape(filename, files[num_files].pfilename, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR); files[num_files].nrSectorsShown = nrSectorsShown; files[num_files].filetype = filetype; files[num_files].direntryindex = -1; files[num_files].mode |= MODE_NOFILE; first_sector_new_track = default_first_sector_new_track; filename = NULL; sectorInterleave = 0; nrSectorsShown = -1; filetype = 0x82; filetype_set = false; num_files++; modified = 1; } else if (strcmp(argv[j], "-x") == 0) { dirtracksplit = 0; } else if (strcmp(argv[j], "-t") == 0) { usedirtrack = 1; } else if (strcmp(argv[j], "-d") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%u", &shadowdirtrack)) { fprintf(stderr, "ERROR: Error parsing argument for -d\n"); return -1; } modified = 1; } else if (strcmp(argv[j], "-u") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &numdirblocks)) { fprintf(stderr, "ERROR: Error parsing argument for -u\n"); return -1; } } else if (strcmp(argv[j], "-B") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &nrSectorsShown)) { fprintf(stderr, "ERROR: Error parsing argument for -B\n"); return -1; } if (nrSectorsShown < 0 || nrSectorsShown > 65535) { fprintf(stderr, "ERROR: Argument must be between 0 and 65535 for -B\n"); return -1; } } else if (strcmp(argv[j], "-4") == 0) { type = IMAGE_D64_EXTENDED_SPEED_DOS; modified = 1; } else if (strcmp(argv[j], "-R") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &restore_level)) { fprintf(stderr, "ERROR: Error parsing argument for -R\n"); return -1; } if(restore_level < 0 || restore_level > 5) { fprintf(stderr, "ERROR: Argument must be between 0 and 5 for -R\n"); return -1; } } else if (strcmp(argv[j], "-5") == 0) { type = IMAGE_D64_EXTENDED_DOLPHIN_DOS; modified = 1; } else if (strcmp(argv[j], "-a") == 0) { print_art_commandline = true; } else if(strcmp(argv[j], "-g") == 0) { if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -g\n"); return -1; } filename_g64 = argv[++j]; } else if (strcmp(argv[j], "-U") == 0) { if ((argc < j + 2) || !sscanf(argv[++j], "%d", &unicode)) { fprintf(stderr, "ERROR: Error parsing argument for -U\n"); return -1; } if(unicode < 0 || unicode > 2) { fprintf(stderr, "ERROR: Argument must be between 0 and 2 for -U\n"); return -1; } } else if (strcmp(argv[j], "-p") == 0) { if(num_patches >= MAXNUMPATCHES) { fprintf(stderr, "ERROR: Too many patches, maximum supported number is %d\n", MAXNUMPATCHES); return -1; } if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -p\n"); return -1; } int numparsed; if((sscanf(argv[j + 1], "%d,%d,%d%n", &(patches[num_patches].track), &(patches[num_patches].sector), &(patches[num_patches].offset), &numparsed) != 3) || (argv[j + 1][numparsed] != ',')) { fprintf(stderr, "ERROR: Error parsing arguments for -p\n"); return -1; } patches[num_patches++].filename = argv[j + 1] + numparsed + 1; modified = 1; j++; } else if (strcmp(argv[j], "-X") == 0) { if(num_extractions >= MAXNUMEXTRACTIONS) { fprintf(stderr, "ERROR: Too many file extraction patterns, maximum supported number is %d\n", MAXNUMPATCHES); return -1; } if (argc < j + 2) { fprintf(stderr, "ERROR: Error parsing argument for -X\n"); return -1; } extractions[num_extractions++] = argv[j + 1]; j++; } else if (strcmp(argv[j], "-q") == 0) { quiet = 1; } else if (strcmp(argv[j], "-v") == 0) { verbose = 1; } else if (strcmp(argv[j], "-h") == 0) { usage(); } else { fprintf(stderr, "ERROR: Error parsing command line at \"%s\"\n", argv[j]); printf("Use -h for help.\n"); return -1; } } if (j >= argc) { fprintf(stderr, "ERROR: No image file provided, or misparsed last option\n"); return -1; } imagepath = argv[argc-1]; if (strlen(imagepath) >= 4) { if (strcmp(imagepath + strlen(imagepath) - 4, ".d71") == 0) { if ((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) { fprintf(stderr, "ERROR: Extended .d71 images are not supported\n"); return -1; } type = IMAGE_D71; } else if (strcmp(imagepath + strlen(imagepath) - 4, ".d81") == 0) { if ((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) { fprintf(stderr, "ERROR: Extended .d81 images are not supported\n"); return -1; } type = IMAGE_D81; dir_sector_interleave = 1; } } if(type != IMAGE_D71 && cluster_optimized) { fprintf(stderr, "ERROR: -c switch is only supported for D71 images\n"); return -1; } if(bam_message != NULL && type != IMAGE_D64 && type != IMAGE_D64_EXTENDED_SPEED_DOS) { fprintf(stderr, "ERROR: Bam message only supported for D64 and SPEED DOS images\n"); return -1; } if(shadowdirtrack > image_num_tracks(type) || (int)shadowdirtrack == dirtrack(type) || (type == IMAGE_D71 && (int)shadowdirtrack == dirtrack(type) + D64NUMTRACKS)) { fprintf(stderr, "ERROR: Invalid shadow directory track\n"); return -1; } if(!transwarp_set) { allocation_strategy = 0; } if (type != IMAGE_D64) { if (transwarp_set && (type != IMAGE_D64_EXTENDED_SPEED_DOS) && (type != IMAGE_D64_EXTENDED_DOLPHIN_DOS)) { fprintf(stderr, "ERROR: Transwarp encoding is not supported for non-D64 images\n"); return -1; } if (filename_g64 != NULL) { fprintf(stderr, "ERROR: G64 output is only supported for non-extended D64 images\n"); return -1; } } /* Check for unsupported settings for D81 */ if (type == IMAGE_D81) { if (default_sector_interleave_set) { fprintf(stderr, "ERROR: -S is not supported for D81 images\n"); return -1; } if (sector_interleave_set) { fprintf(stderr, "ERROR: -s is not supported for D81 images\n"); return -1; } if (new_track_start_sector_set) { fprintf(stderr, "ERROR: -F is not supported for D81 images\n"); return -1; } if (file_start_sector_set) { fprintf(stderr, "ERROR: -b is not supported for D81 images\n"); return -1; } } /* Change locale from C to default to allow unicode printouts */ if(unicode != 0) { setlocale(LC_ALL, ""); } /* quiet has precedence over verbose */ if(quiet) { verbose = 0; } /* open image */ unsigned int imagesize = image_size(type); unsigned char* image = (unsigned char*)calloc(imagesize, sizeof(unsigned char)); if (image == NULL) { fprintf(stderr, "ERROR: Memory allocation error\n"); return -1; } FILE* f = fopen(imagepath, "rb"); if (f == NULL) { modified = 1; if (!quiet) { printf("Adding %d files to new image %s\n", num_files, basename((unsigned char*)imagepath)); } initialize_directory(type, image, header, id, bam_message, shadowdirtrack, usedirtrack); } else { if (!quiet) { printf("Adding %d files to existing image %s\n", num_files, basename((unsigned char*)imagepath)); } size_t read_size = fread(image, 1, imagesize, f); fclose(f); if (read_size != imagesize) { if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (read_size == D64SIZE)) { /* Clear extra tracks */ memset(image + image_size(IMAGE_D64), 0, image_size(type) - image_size(IMAGE_D64)); /* Mark all extra sectors unused */ for (unsigned int t = D64NUMTRACKS + 1; t <= image_num_tracks(type); t++) { for (int s = 0; s < num_sectors(type, t); s++) { mark_sector(type, image, t, s, 1 /* free */); } } } else { fprintf(stderr, "ERROR: Wrong filesize: expected to read %u bytes, but read %u bytes\n", imagesize, (unsigned int) read_size); return -1; } } if (dovalidate) { validate(type, image); } if (restore_level >= 0) { restore(type, image, restore_level, files); } if (set_header) { update_directory(type, image, header, id, bam_message, shadowdirtrack); } } /* Print command line before adding anything to the image */ if(print_art_commandline) { convert_to_commandline(type, image); } /* Extract files */ extract_files(type, image, extractions, num_extractions); /* Apply patches */ apply_patches(type, image, patches, num_patches); /* Create directory entries */ create_dir_entries(type, image, files, num_files, dir_sector_interleave, shadowdirtrack, nooverwrite); /* Write files and mark sectors in BAM */ write_files(type, image, files, num_files, usedirtrack, dirtracksplit, shadowdirtrack, numdirblocks, dir_sector_interleave, allocation_strategy); /* Print allocation info */ if (verbose) { print_file_allocation(type, image, files, num_files); } int blocks_free = check_bam(type, image); /* Print directory */ if (!quiet) { print_directory(type, image, blocks_free); } /* Show directory issues if present */ if(dir_error != DIR_OK) { fprintf(stdout, "WARNING: %s\n", dir_error_string[dir_error]); } /* Save image */ if(modified) { f = fopen(imagepath, "wb"); if (f == NULL || fwrite(image, imagesize, 1, f) != 1) { fprintf(stderr, "ERROR: Failed to write %s\n", imagepath); retval = -1; } if (f != NULL) { fclose(f); } } /* Save optional g64 image */ if (filename_g64 != NULL) { /* retval might be set to -1 already. Thus we need to take its previous state and OR it with the following return value. */ retval |= generate_uniformat_g64(image, filename_g64); } if (!ignore_collision && check_hashes(type, image)) { fprintf(stderr, "\nERROR: Filename hash collision detected; image is not compatible with Krill's loader. Use -m to ignore this error.\n"); retval = -1; } free(image); return retval; } cc1541-4.2/test_cc1541.c0000644000000000000000000031610014567574546013110 0ustar rootroot/******************************************************************************* * Copyright (c) 2018-2024 Claus, Björn Esser * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ #define _CRT_SECURE_NO_WARNINGS /* avoid security warnings for MSVC */ #include #include #include #include #include #define CMD_LINE_LEN 4096 + 1 /* 4k is enough for commandline buffer. */ #define MERGE_OUT_ERR " 2>&1" #ifdef _WIN32 #define FILESEPARATOR "\\" #define NULL_DEV "> nul" #else #define FILESEPARATOR "/" #define NULL_DEV "> /dev/null" #endif enum { NO_ERROR = 0, ERROR_ALLOCATION, ERROR_RETURN_VALUE, ERROR_NO_OUTPUT }; const unsigned int track_offset[] = { /* taken from http://unusedino.de/ec64/technical/formats/d64.html */ 0x00000, 0x01500, 0x02A00, 0x03F00, 0x05400, 0x06900, 0x07E00, 0x09300, 0x0A800, 0x0BD00, 0x0D200, 0x0E700, 0x0FC00, 0x11100, 0x12600, 0x13B00, 0x15000, 0x16500, 0x17800, 0x18B00, 0x19E00, 0x1B100, 0x1C400, 0x1D700, 0x1EA00, 0x1FC00, 0x20E00, 0x22000, 0x23200, 0x24400, 0x25600, 0x26700, 0x27800, 0x28900, 0x29A00, 0x2AB00, 0x2BC00, 0x2CD00, 0x2DE00, 0x2EF00 }; const unsigned int track_offset_b[] = { /* second side of D71 */ 0x2AB00 + 0x00000, 0x2AB00 + 0x01500, 0x2AB00 + 0x02A00, 0x2AB00 + 0x03F00, 0x2AB00 + 0x05400, 0x2AB00 + 0x06900, 0x2AB00 + 0x07E00, 0x2AB00 + 0x09300, 0x2AB00 + 0x0A800, 0x2AB00 + 0x0BD00, 0x2AB00 + 0x0D200, 0x2AB00 + 0x0E700, 0x2AB00 + 0x0FC00, 0x2AB00 + 0x11100, 0x2AB00 + 0x12600, 0x2AB00 + 0x13B00, 0x2AB00 + 0x15000, 0x2AB00 + 0x16500, 0x2AB00 + 0x17800, 0x2AB00 + 0x18B00, 0x2AB00 + 0x19E00, 0x2AB00 + 0x1B100, 0x2AB00 + 0x1C400, 0x2AB00 + 0x1D700, 0x2AB00 + 0x1EA00, 0x2AB00 + 0x1FC00, 0x2AB00 + 0x20E00, 0x2AB00 + 0x22000, 0x2AB00 + 0x23200, 0x2AB00 + 0x24400, 0x2AB00 + 0x25600, 0x2AB00 + 0x26700, 0x2AB00 + 0x27800, 0x2AB00 + 0x28900, 0x2AB00 + 0x29A00, 0x2AB00 + 0x2AB00, 0x2AB00 + 0x2BC00, 0x2AB00 + 0x2CD00, 0x2AB00 + 0x2DE00, 0x2AB00 + 0x2EF00 }; /* Runs the binary with the provided commandline and returns the content of the output image file in a buffer */ int run_binary(const char* binary, const char* options, const char* image_name, char **image, size_t *size, bool silent) { struct stat st; static char command_line[CMD_LINE_LEN]; if (*image != NULL) { free(*image); *image = NULL; } /* build command line */ if(silent) { snprintf(command_line, CMD_LINE_LEN, "%s %s %s %s %s", binary, options, image_name, NULL_DEV, MERGE_OUT_ERR); } else { snprintf(command_line, CMD_LINE_LEN, "%s %s %s %s", binary, options, image_name, NULL_DEV); } if (system(command_line) != 0) { return ERROR_RETURN_VALUE; } if (stat(image_name, &st)) { return ERROR_NO_OUTPUT; } *size = st.st_size; *image = calloc(st.st_size, sizeof(unsigned char)); FILE* f = fopen(image_name, "rb"); if (f == NULL) { return ERROR_NO_OUTPUT; } if (fread(*image, *size, 1, f) != 1) { fprintf(stderr, "ERROR: Unexpected filesize when reading %s\n", image_name); return ERROR_NO_OUTPUT; } fclose(f); return NO_ERROR; } /* runs the binary with a given command line and image output file, reads the output into a buffer and deletes the file then */ int run_binary_cleanup(const char* binary, const char* options, const char* image_name, char **image, size_t *size, bool silent) { int status = run_binary(binary, options, image_name, image, size, silent); remove(image_name); return status; } /* writes image to file */ int write_file(const char* name, size_t size, char *image) { FILE* f = fopen(name, "wb"); if(f == NULL) { fprintf(stderr, "ERROR: Could not create output file %s\n", name); return ERROR_NO_OUTPUT; } for (size_t i = 0; i < size; i++) { fputc(image[i], f); } fclose(f); return NO_ERROR; } /* creates a file with given name, size and filled with given value */ int create_value_file(const char* name, int size, char value) { FILE* f = fopen(name, "wb"); if (f == NULL) { fprintf(stderr, "ERROR: Could not create output file %s\n", name); return ERROR_NO_OUTPUT; } for (int i = 0; i < size; i++) { fputc(value, f); } fclose(f); return NO_ERROR; } /* checks if a given block in the image is filled with a given value */ int block_is_filled(char* image, int block, int value) { for (int i = 0; i < 254; i++) { if (image[block * 256 + 2 + i] != value) { return 0; } } return 1; } int main(int argc, char* argv[]) { struct stat st; const char* binary; char *image = NULL; size_t size; int test = 0; int passed = 0; char *description; int result = 0; enum { TEST_PASS = 0, TEST_FAIL = 1, TEST_UNRESOLVED = 2 }; const char *const result_str[] = { "PASS", "FAIL", "UNRESOLVED" }; const int test_pad = 3; /* Decimal digits of the test counter */ if (argc != 2) { printf("Test suite for cc1541\n"); printf("Usage: test_cc1541 \n"); return(-1); } if (stat(argv[1], &st)) { printf("ERROR: Test binary %s does not exist.\n", argv[1]); return(-1); } binary = argv[1]; description = "Size of empty D64 image should be 174848"; ++test; if (run_binary_cleanup(binary, "", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (size == 174848) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Size of empty G64 image should be 269862"; ++test; if (run_binary_cleanup(binary, "-g image.g64", "image.g64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (size == 269862) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Size of empty D71 image should be 2*174848"; ++test; if (run_binary_cleanup(binary, "", "image.d71", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (size == 2 * 174848) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Size of empty Speed DOS D64 image should be 174848+5*17*256"; ++test; if (run_binary_cleanup(binary, "-4", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (size == 174848 + 5 * 17 * 256) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Size of empty Dolphin DOS D64 image should be 174848+5*17*256"; ++test; if (run_binary_cleanup(binary, "-5", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (size == 174848 + 5 * 17 * 256) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Size of empty D81 image should be 80*40*256"; ++test; if (run_binary_cleanup(binary, "", "image.d81", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (size == 80 * 40 * 256) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Writing file with one block should fill track 1 sector 0"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 0, 37) && image[0 * 256] == 0 && image[0 * 256 + 1] == (char)255) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Diskname should be found in track 18 sector 0 offset $90"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-n 0123456789abcdef -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + 0x90], "0123456789ABCDEF", 16) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Diskname should be found in track 40 sector 0 offset 4 for d81"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-n 0123456789abcdef -w 1.prg", "image.d81", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[40*39*256 + 4], "0123456789ABCDEF", 16) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Diskname should be truncated to 16 characters"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-n 0123456789abcdef -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + 0x90], "0123456789ABCDEF", 16) == 0 && image[track_offset[17] + 0xa0] == (char)0xa0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Diskname hex escape should be evaluated correctly"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-n 0123456789abcde#ef -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 0x90 + 15] == (char)0xef) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Disk ID should be found in track 18 sector 0 offset $a2"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-i 01234 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + 0xa2], "01234", 5) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Disk ID should be found in track 40 sector 0 offset 0x16 for d81"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-i 01234 -w 1.prg", "image.d81", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[40 * 39 * 256 + 0x16], "01234", 5) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Disk ID should be truncated to 5 characters"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-i 0123456789ABCDEFGHI -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + 0xa2], "01234", 5) == 0 && image[track_offset[17] + 0xa7] == (char)0xa0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Disk ID hex escape should be evaluated correctly"; ++test; create_value_file("1.prg", 254, 37); if (run_binary_cleanup(binary, "-i 0123#ef -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 0xa2 + 4] == (char)0xef) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Setting minimum sector to 7 should fill track 1 sector 7"; ++test; create_value_file("1.prg", 254 * 1, 1); if (run_binary_cleanup(binary, "-F 7 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 7, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Setting minimum sector to 7 for second track should fill track 2 sector 7"; ++test; create_value_file("1.prg", 254 * 21, 1); create_value_file("2.prg", 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -F 7 -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 21 + 7, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Setting minimum sector to 7 should fill track 2 sector 7 for longer file"; ++test; create_value_file("1.prg", 254 * 22, 1); if (run_binary_cleanup(binary, "-F 7 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 21 + 7, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Setting minimum sector to negative for second track should fill track 2 sector 10"; ++test; create_value_file("1.prg", 254 * 22, 1); if (run_binary_cleanup(binary, "-F -3 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 21 + 4, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Minimum sector should fall back to 0 after track change"; ++test; create_value_file("1.prg", 254 * 21, 1); create_value_file("2.prg", 254, 2); if (run_binary_cleanup(binary, "-F 7 -w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 21 + 0, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File with default sector interleave 7 should fill sector 0 and 7 on track 1"; ++test; create_value_file("1.prg", 254 * 2, 37); if (run_binary_cleanup(binary, "-S 7 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 0, 37) && block_is_filled(image, 7, 37)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File with sector interleave 9 should fill sector 0 and 9 on track 1"; ++test; create_value_file("1.prg", 254 * 2, 37); if (run_binary_cleanup(binary, "-s 9 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 0, 37) && block_is_filled(image, 9, 37)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File with sector interleave 20 should fill sector 3 and 1 on track 1"; ++test; create_value_file("1.prg", 254 * 2, 37); if (run_binary_cleanup(binary, "-F 3 -s 20 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 3, 37) && block_is_filled(image, 2, 37)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Sector interleave should go back to default for next file"; ++test; create_value_file("1.prg", 254 * 2, 1); create_value_file("2.prg", 254 * 2, 2); if (run_binary_cleanup(binary, "-S 3 -s 2 -w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 4, 2) && block_is_filled(image, 7, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Filename should be found at track 18 sector 1 offset 5"; ++test; create_value_file("1.prg", 254 * 2, 1); if (run_binary_cleanup(binary, "-f 0123456789abcdef -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + 256 + 5], "0123456789ABCDEF", 16) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Filename hexvalue should be interpreted correctly"; ++test; create_value_file("1.prg", 254 * 2, 1); if (run_binary_cleanup(binary, "-f 0123456789ABCDE#ef -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 5 + 15] == (char)0xef) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Filename capital hexvalue should be interpreted correctly"; ++test; create_value_file("1.prg", 254 * 2, 1); if (run_binary_cleanup(binary, "-f 0123456789ABCDE#EF -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 5 + 15] == (char)0xef) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Input path should be stripped of folders for filename"; ++test; create_value_file(".." FILESEPARATOR "1.prg", 254 * 2, 1); if (run_binary_cleanup(binary, "-w .." FILESEPARATOR "1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + 256 + 5], "1.PRG", 5) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("../1.prg"); description = "Second file should start on track 2 sector 10 for -e"; ++test; create_value_file("1.prg", 254, 1); create_value_file("2.prg", 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -e -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 0 + 21 + 10, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Second file should start on track 2 sector 3 for -e -b 3"; ++test; create_value_file("1.prg", 254, 1); create_value_file("2.prg", 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -e -b 3 -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 3 + 21, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Second file should start on track 1 when it fits for -E"; ++test; create_value_file("1.prg", 20 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -E -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 11, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Second file should not start on track 1 when it does not fit for -E"; ++test; create_value_file("1.prg", 20 * 254, 1); create_value_file("2.prg", 2 * 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -E -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 11, 0)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File should start on track 13 for -r"; ++test; create_value_file("1.prg", 254, 1); if (run_binary_cleanup(binary, "-r 13 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[12] / 256 + 0, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Transwarp file should start on track 13 for -r"; ++test; create_value_file("1.prg", 254, 1); if (run_binary_cleanup(binary, "-r 13 -W 1.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (!block_is_filled(image, track_offset[12] / 256 + 0, 0)) { /* not the best test, check only if not 0 */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Next file should be allocated in track 0 after -r"; ++test; create_value_file("1.prg", 254, 1); if (run_binary_cleanup(binary, "-r 13 -f 1 -w 1.prg -f 2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[0] / 256 + 10, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File should start on sector 14 for -b"; ++test; create_value_file("1.prg", 254, 1); create_value_file("2.prg", 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -b 14 -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 14, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File should be distributed to both sides for -c"; ++test; create_value_file("1.prg", 22 * 254, 1); if (run_binary_cleanup(binary, "-c -w 1.prg", "image.d71", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset_b[0] / 256 + 0, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File should cover 1 sector on track 19 for -x not set"; ++test; create_value_file("1.prg", 356 * 254, 1); /* leaves only one sector free before track 18 */ create_value_file("2.prg", 2 * 254, 2); if (run_binary_cleanup(binary, "-w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[18] / 256 + 13, 0)) { /* check only second sector */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File should cover 2 sectors on track 19 for -x"; ++test; create_value_file("1.prg", 356 * 254, 1); /* leaves only one sector free before track 18 */ create_value_file("2.prg", 2 * 254, 2); if (run_binary_cleanup(binary, "-x -w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[18] / 256 + 10, 2)) { /* check only second sector */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File should be placed on track 18 for -t"; ++test; create_value_file("1.prg", 357 * 254, 1); /* fills all tracks up to 18 */ create_value_file("2.prg", 2 * 254, 2); if (run_binary_cleanup(binary, "-t -w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[17] / 256 + 12, 2)) { /* check only second sector, 2 sectors are filled by dir */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File with 3 sectors should be placed on track 18 for -u"; ++test; create_value_file("1.prg", 357 * 254, 1); /* fills all tracks up to 18 */ create_value_file("2.prg", 3 * 254, 2); if (run_binary_cleanup(binary, "-t -u 3 -w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[17] / 256 + 3 /* (3+10+10)%19-1 */, 2)) { /* check only third sector */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Track 23 sector 0 and 1 should be identical to track 18 for -d"; ++test; create_value_file("1.prg", 3 * 254, 1); create_value_file("2.prg", 5 * 254, 2); if (run_binary_cleanup(binary, "-d 23 -w 1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (memcmp(&image[track_offset[17] + 1], &image[track_offset[22] + 1], 1) == 0 /* shadow BAM is not valid, only need the sector link */ && memcmp(&image[track_offset[17] + 1] + 256, &image[track_offset[22] + 1] + 256, 255) == 0) { /* leave out next dir block track */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "File should have DIR block size 0 for -B"; ++test; create_value_file("1.prg", 3 * 254, 1); if (run_binary_cleanup(binary, "-B 0 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 30] == 0 && image[track_offset[17] + 256 + 31] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File should have DIR block size 65535 for -B"; ++test; create_value_file("1.prg", 3 * 254, 1); if (run_binary_cleanup(binary, "-B 65535 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 30] == (char)255 && image[track_offset[17] + 256 + 31] == (char)255) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Loop file should have actual DIR block size per default"; ++test; create_value_file("1.prg", 258 * 254, 1); if (run_binary_cleanup(binary, "-w 1.prg -f loop -l 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 32 + 30] == 2 && image[track_offset[17] + 256 + 32 + 31] == 1) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Loop file should have DIR block size 258 for -B"; ++test; create_value_file("1.prg", 39 * 254, 1); if (run_binary_cleanup(binary, "-w 1.prg -f loop -B 258 -l 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 32 + 30] == 2 && image[track_offset[17] + 256 + 32 + 31] == 1) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File should have DIR block size 258 for -B, but actual block size in shadow dir for -d"; ++test; create_value_file("1.prg", 3 * 254, 1); if (run_binary_cleanup(binary, "-B 258 -d 23 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 30] == 258%256 && image[track_offset[17] + 256 + 31] == 258/256 && image[track_offset[22] + 256 + 30] == 3 && image[track_offset[22] + 256 + 31] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Loop file should share track and sector with file using wildcard * for -l"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f loop -l 1.* -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ((image[track_offset[17] + 256 + 3] == image[track_offset[17] + 256 + 32 + 3]) && (image[track_offset[17] + 256 + 4] == image[track_offset[17] + 256 + 32 + 4])) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Loop file should share track and sector with file using wildcard ? for -l"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f loop -l 1.p?g -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ((image[track_offset[17] + 256 + 3] == image[track_offset[17] + 256 + 32 + 3]) && (image[track_offset[17] + 256 + 4] == image[track_offset[17] + 256 + 32 + 4])) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Loop file should share track and sector with later file using -l"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f loop -l 1.prg -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ((image[track_offset[17] + 256 + 3] == image[track_offset[17] + 256 + 32 + 3]) && (image[track_offset[17] + 256 + 4] == image[track_offset[17] + 256 + 32 + 4])) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Loop file should share track and sector with file using -l when modifying image"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-f loop -l file", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ((image[track_offset[17] + 256 + 3] == image[track_offset[17] + 256 + 32 + 3]) && (image[track_offset[17] + 256 + 4] == image[track_offset[17] + 256 + 32 + 4])) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing a PRG should result in first two blocks allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-F 0 -S 1 -f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfc) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing a PRG should result in first two blocks allocated on d81"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f file -w 1.prg", "image.d81", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[40*39*256+256 + 6 + 11] == (char)0xfc) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing a DEL should result in first two blocks allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-T DEL -F 0 -S 1 -f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfc) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing two files with type 0 should result in first two blocks allocated"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-T 0 -S 1 -F 0 -f 1 -w 1.prg -T 0 -f 2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfc) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing no file with -L should create dir entry, but not allocate any block"; ++test; if (run_binary_cleanup(binary, "-F 0 -S 1 -f file -L", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ((image[track_offset[17] + 256 + 2] == (char)0x82) && image[track_offset[17] + 5] == (char)0xff) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Overwriting a PRG should result in only first block allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary(binary, "-F 0 -S 1 -f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-F 0 -S 1 -f file -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfe) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Overwriting a non-standard file type should result in only first block allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary(binary, "-F 0 -S 1 -T 69 -f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-F 0 -S 1 -f file -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfe) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Overwriting a PRG on d81 should result in only first block allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary(binary, "-f file -w 1.prg", "image.d81", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-f file -w 2.prg", "image.d81", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[40 * 39 * 256 + 256 + 6 + 11] == (char)0xfe) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Overwriting a PRG with a USR should result in only first block allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary(binary, "-F 0 -S 1 -f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-T USR -F 0 -S 1 -f file -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfe) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Overwriting a PRG with a DEL should result in only first block allocated"; ++test; create_value_file("1.prg", 2 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary(binary, "-F 0 -S 1 -f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-T DEL -F 0 -S 1 -f file -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 5] == (char)0xfe) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Protect flag should be set for -P"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-P -f file1 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 2] == (char)0xc2) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Protect flag should be set for transwarp file for -P"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-P -f file1 -W 1.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 2] == (char)0xc2) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Open flag should be set for -O"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-O -f file1 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 2] == (char)0x02) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Open flag should be set for transwarp file for -O"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-O -f file1 -W 1.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 2] == (char)0x02) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "After having set type, open and protected flag next file should go back to normal PRG as default"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-T USR -P -O -f file1 -w 1.prg -f file2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 32 + 2] == (char)0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "After having set type, open and protected flag next file should go back to normal PRG as default"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-T USR -P -O -f file1 -w 1.prg -f file2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 32 + 2] == (char)0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "File should be overwritten even if there is a free dir slot before"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary(binary, "-f file1 -w 1.prg -f file2 -w 1.prg -f file1 -T DEL -O -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-f file2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 +2] == (char)0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing 9 files should allocate new dir sector"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg -f 3 -w 1.prg -f 4 -w 1.prg -f 5 -w 1.prg -f 6 -w 1.prg -f 7 -w 1.prg -f 8 -w 1.prg -f 9 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 256 + 3*256 + 2] == (char)0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Directory should allow for 144 entries"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary(binary, "-f 00 -w 1.prg -f 01 -w 1.prg -f 02 -w 1.prg -f 03 -w 1.prg -f 04 -w 1.prg -f 05 -w 1.prg -f 06 -w 1.prg -f 07 -w 1.prg -f 08 -w 1.prg -f 09 -w 1.prg -f 0a -w 1.prg -f 0b -w 1.prg -f 0c -w 1.prg -f 0d -w 1.prg -f 0e -w 1.prg -f 0f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 10 -w 1.prg -f 11 -w 1.prg -f 12 -w 1.prg -f 13 -w 1.prg -f 14 -w 1.prg -f 15 -w 1.prg -f 16 -w 1.prg -f 17 -w 1.prg -f 18 -w 1.prg -f 19 -w 1.prg -f 1a -w 1.prg -f 1b -w 1.prg -f 1c -w 1.prg -f 1d -w 1.prg -f 1e -w 1.prg -f 1f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 20 -w 1.prg -f 21 -w 1.prg -f 22 -w 1.prg -f 23 -w 1.prg -f 24 -w 1.prg -f 25 -w 1.prg -f 26 -w 1.prg -f 27 -w 1.prg -f 28 -w 1.prg -f 29 -w 1.prg -f 2a -w 1.prg -f 2b -w 1.prg -f 2c -w 1.prg -f 2d -w 1.prg -f 2e -w 1.prg -f 2f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 30 -w 1.prg -f 31 -w 1.prg -f 32 -w 1.prg -f 33 -w 1.prg -f 34 -w 1.prg -f 35 -w 1.prg -f 36 -w 1.prg -f 37 -w 1.prg -f 38 -w 1.prg -f 39 -w 1.prg -f 3a -w 1.prg -f 3b -w 1.prg -f 3c -w 1.prg -f 3d -w 1.prg -f 3e -w 1.prg -f 3f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 40 -w 1.prg -f 41 -w 1.prg -f 42 -w 1.prg -f 43 -w 1.prg -f 44 -w 1.prg -f 45 -w 1.prg -f 46 -w 1.prg -f 47 -w 1.prg -f 48 -w 1.prg -f 49 -w 1.prg -f 4a -w 1.prg -f 4b -w 1.prg -f 4c -w 1.prg -f 4d -w 1.prg -f 4e -w 1.prg -f 4f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 50 -w 1.prg -f 51 -w 1.prg -f 52 -w 1.prg -f 53 -w 1.prg -f 54 -w 1.prg -f 55 -w 1.prg -f 56 -w 1.prg -f 57 -w 1.prg -f 58 -w 1.prg -f 59 -w 1.prg -f 5a -w 1.prg -f 5b -w 1.prg -f 5c -w 1.prg -f 5d -w 1.prg -f 5e -w 1.prg -f 5f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 60 -w 1.prg -f 61 -w 1.prg -f 62 -w 1.prg -f 63 -w 1.prg -f 64 -w 1.prg -f 65 -w 1.prg -f 66 -w 1.prg -f 67 -w 1.prg -f 68 -w 1.prg -f 69 -w 1.prg -f 6a -w 1.prg -f 6b -w 1.prg -f 6c -w 1.prg -f 6d -w 1.prg -f 6e -w 1.prg -f 6f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary(binary, "-f 70 -w 1.prg -f 71 -w 1.prg -f 72 -w 1.prg -f 73 -w 1.prg -f 74 -w 1.prg -f 75 -w 1.prg -f 76 -w 1.prg -f 77 -w 1.prg -f 78 -w 1.prg -f 79 -w 1.prg -f 7a -w 1.prg -f 7b -w 1.prg -f 7c -w 1.prg -f 7d -w 1.prg -f 7e -w 1.prg -f 7f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-f 80 -w 1.prg -f 81 -w 1.prg -f 82 -w 1.prg -f 83 -w 1.prg -f 84 -w 1.prg -f 85 -w 1.prg -f 86 -w 1.prg -f 87 -w 1.prg -f 88 -w 1.prg -f 89 -w 1.prg -f 8a -w 1.prg -f 8b -w 1.prg -f 8c -w 1.prg -f 8d -w 1.prg -f 8e -w 1.prg -f 8f -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (strncmp(&image[track_offset[17] + (1+18*3)%19*256 + 7*32 + 5], "8F", 2) == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing a duplicate DIR entry with -N should write a new file"; ++test; create_value_file("1.prg", 1 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -N -f 1 -w 2.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 10, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Writing a duplicate DIR entry with -o should return an error"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary(binary, "-m -f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-m -o -f 1 -w 1.prg ", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Sector chain with invalid track link should be re-added to dir but left invalid for -R 0"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[10 * 256] = 36; /* invalid track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 0 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && image[10 * 256] == 36) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring scratched first file should regenerate the dir entry for -R 1"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratched */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring huge scratched file should regenerate the dir entry for -R 1"; ++test; create_value_file("1.prg", 664 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratched */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring scratched last file should regenerate the dir entry for -R 1"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg -f 3 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2*32 + 2] = 0; /* scratched */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2*32 + 2] == 0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring scratched mid file should regenerate the dir entry for -R 1"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg -f 3 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratched */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 1*32 + 2] == 0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring scratched file should result in correct file size for -R 1"; ++test; create_value_file("1.prg", (1*256 + 13) * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 30] = 0; /* delete file size */ image[track_offset[17] + 256 + 31] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 30] == 13 && (unsigned char)image[track_offset[17] + 256 + 31] == 1) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring 10 files with same name should create correct filename appendix for -R 1"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-m -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg -N -f file -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 2*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 4*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 5*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 6*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 7*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 4*256 + 0*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 4*256 + 1*32 + 2] = 0; /* scratch file */ image[track_offset[17] + 4*256 + 2*32 + 2] = 0; /* scratch file */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 4*256 + 2*32 + 10] == '1' && (unsigned char)image[track_offset[17] + 4*256 + 2*32 + 11] == '0') { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring unlinked dir sector should link it for -R 1"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg -f 3 -w 1.prg -f 4 -w 1.prg -f 5 -w 1.prg -f 6 -w 1.prg -f 7 -w 1.prg -f 8 -w 1.prg -f 9 -w 1.prg -f 10 -w 1.prg -f 11 -w 1.prg -f 12 -w 1.prg -f 13 -w 1.prg -f 14 -w 1.prg -f 15 -w 1.prg -f 16 -w 1.prg -f 17 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 1*256 + 1] = 7; /* make first sector link point to third dir sector */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 7*256 + 1] == 4) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Scratched file with conflicting t/s chain should be ignored for -R 1"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[20*256+1] = 10; /* make t/s link point to second sector of first file */ image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratch second file */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 1*32 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring should be robust against invalid track pointer in directory for -R 1"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 1 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { result = TEST_PASS; ++passed; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild valid sector chain should be restored for -R 2"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = (unsigned char)0xff; /* overwrite track pointer */ image[track_offset[17] + 256 + 4] = (unsigned char)0xff; /* overwrite sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && (unsigned char)image[track_offset[17] + 256 + 3] == 1 && (unsigned char)image[track_offset[17] + 256 + 4] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Restoring wild sector chain should result in correct file size for -R 2"; ++test; create_value_file("1.prg", (1*256 + 13) * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ image[track_offset[17] + 256 + 30] = 0; /* delete file size */ image[track_offset[17] + 256 + 31] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 30] == 13 && (unsigned char)image[track_offset[17] + 256 + 31] == 1) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with invalid track link should be ignored for -R 2"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ image[10 * 256] = 36; /* invalid track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with invalid sector link should be ignored for -R 2"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ image[10 * 256 + 0] = 1; image[10 * 256 + 1] = 21; /* invalid sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with conflict should be ignored for -R 2"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[20*256+1] = 10; /* make t/s link point to second sector of first file */ image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratch second file */ image[track_offset[17] + 256 + 1*32 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 1*32 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 1*32 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with loop should be ignored for -R 2"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[10*256+0] = 1; /* make t/s link loop to first block */ image[10*256+1] = 0; image[track_offset[17] + 256 + 2] = 0; /* scratch second file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild valid single sector should be ignored for -R 2"; ++test; create_value_file("1.prg", 1 * 113, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 2 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Scratched file with invalid track link should be fixed for -R 3"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[10 * 256] = 36; /* invalid track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 3 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && image[10 * 256] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Fixed file with long name should receive marker for -R 3"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 0123456789123456 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[10 * 256] = 36; /* invalid track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 3 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 20] == '<') { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Fixed file with existing name should receive appendix and marker for -R 3"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-m -f file1 -w 1.prg -N -f \"file1<\" -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[10 * 256] = 36; /* invalid track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 3 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 13] == '<') { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Conflicting t/s chain should result in correct file size for -R 3"; ++test; create_value_file("1.prg", 2 * 254, 1); create_value_file("2.prg", 2 * 254, 2); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 2.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[20*256+1] = 10; /* make t/s link point to second sector of first file */ image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratch second file */ image[track_offset[17] + 256 + 1*32 + 30] = (unsigned char)0xff; /* overwrite file size */ image[track_offset[17] + 256 + 1*32 + 31] = (unsigned char)0xff; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 3 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 +1*32 + 2] == 0x82 && (unsigned char)image[track_offset[17] + 256 + 1*32 + 30] == 1 && (unsigned char)image[track_offset[17] + 256 + 1*32 + 31] == 0) { result = TEST_PASS; ++passed; } else { printf(" (%d/%d) ", (unsigned char)image[track_offset[17] + 256 + 1*32 + 30], (unsigned char)image[track_offset[17] + 256 + 1*32 + 31]); result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with invalid track link should be fixed for -R 4"; ++test; create_value_file("1.prg", 254 + 11, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ image[10 * 256] = 36; /* invalid track pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 4 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && image[10 * 256] == 0 && (unsigned char)image[10 * 256 + 1] == 0xff) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with invalid sector link should be fixed for -R 4"; ++test; create_value_file("1.prg", 254 + 11, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ image[10 * 256 + 0] = 1; image[10 * 256 + 1] = 21; /* invalid sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 4 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && image[10 * 256] == 0 && (unsigned char)image[10 * 256 + 1] == 0xff) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with conflict should be fixed for -R 4"; ++test; create_value_file("1.prg", 3 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[19*256+1] = 10; /* make t/s link point to second sector of first file */ image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratch second file */ image[track_offset[17] + 256 + 1*32 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 1*32 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 4 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 1*32 + 2] == 0x82 && image[19*256] == 0 && (unsigned char)image[19*256+1] == 0xff) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild sector chain with loop should be fixed for -R 4"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[10*256+0] = 1; /* make t/s link loop to first block */ image[10*256+1] = 0; image[track_offset[17] + 256 + 2] = 0; /* scratch second file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 4 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && image[10 * 256] == 0 && (unsigned char)image[10 * 256 + 1] == 0xff) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild valid single sector should be ignored for -R 4"; ++test; create_value_file("1.prg", 1 * 113, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 4 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Scratched file with immediate conflict should be ignored for -R 4"; ++test; create_value_file("1.prg", 3 * 254, 1); create_value_file("2.prg", 1 * 254, 2); if (run_binary_cleanup(binary, "-f 1 -w 1.prg -f 2 -w 2.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 1*32 + 2] = 0; /* scratch second file */ image[track_offset[17] + 256 + 1*32 + 3] = 1; /* let t/s point to first file */ image[track_offset[17] + 256 + 1*32 + 4] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 4 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 1*32 + 2] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Wild valid single sector should be restored for -R 5"; ++test; create_value_file("1.prg", 1 * 113, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 5 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Wild valid single sector should have correct size for -R 5"; ++test; create_value_file("1.prg", 1 * 113, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratch file */ image[track_offset[17] + 256 + 3] = 0; /* delete track pointer */ image[track_offset[17] + 256 + 4] = 0; /* delete sector pointer */ image[track_offset[17] + 256 + 30] = (unsigned char)0xff; /* overwrite file size */ image[track_offset[17] + 256 + 31] = (unsigned char)0xff; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-R 5 ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if((unsigned char)image[track_offset[17] + 256 + 2] == 0x82 && image[track_offset[17] + 256 + 30] == 1 && image[track_offset[17] + 256 + 31] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Recursive DIR sector chain should not run into endless loop"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 0] = 18; /* track link points to dir track */ image[track_offset[17] + 256 + 1] = 1; /* sector link points to itself */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-f 2 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { result = TEST_PASS; ++passed; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Recursive file sector chain should not run into endless loop for overwriting"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[10 * 256 + 0] = 1; /* t/s link points to first block */ image[10 * 256 + 1] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-f 1 -w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { result = TEST_PASS; ++passed; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Should not return error for illegal t/s link in DIR chain"; ++test; create_value_file("1.prg", 254 + 11, 1); if (run_binary_cleanup(binary, "-w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 0] = 18; /* legal track */ image[track_offset[17] + 256 + 1] = 127; /* illegal sector */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "", "image.d64", &image, &size, true) == NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Should not return error for illegal t/s pointer in DIR"; ++test; create_value_file("1.prg", 254 + 11, 1); if (run_binary_cleanup(binary, "-w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 4] = 127; /* illegal sector */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "", "image.d64", &image, &size, true) == NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Verbose output should not return error for illegal t/s pointer in DIR"; ++test; create_value_file("1.prg", 254 + 11, 1); if (run_binary_cleanup(binary, "-w 1.prg ", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 4] = 127; /* illegal sector */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-v", "image.d64", &image, &size, true) == NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Adding a transwarp v0.84 file should allocate track 1 completely"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-r 1 -f file1 -W 1.prg -w \"transwarp v0.84.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 4] == 0 && image[track_offset[17] + 5] == 0 && image[track_offset[17] + 6] == 0 && image[track_offset[17] + 7] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Adding a transwarp v0.86 file should allocate track 1 completely"; ++test; create_value_file("1.prg", 2 * 254, 1); if (run_binary_cleanup(binary, "-r 1 -f file1 -W 1.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17] + 4] == 0 && image[track_offset[17] + 5] == 0 && image[track_offset[17] + 6] == 0 && image[track_offset[17] + 7] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Standard file should be allocated in track 0 for transwarp image"; ++test; create_value_file("1.prg", 1 * 254, 1); if (run_binary_cleanup(binary, "-b 0 -f file1 -w 1.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[0] / 256 + 0, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Sector on new track should not be limited to number of sectors on old track"; ++test; create_value_file("1.prg", 254 * 20, 1); /* track 24 has 19 blocks, track 25 only 18 */ if (run_binary_cleanup(binary, "-r 24 -F 18 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[24]/256, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Last file block should set unused bytes to zero"; ++test; create_value_file("1.prg", 254 * 1, 1); create_value_file("2.prg", 1, 2); if (run_binary_cleanup(binary, "-w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } image[track_offset[17] + 256 + 2] = 0; /* scratched */ image[track_offset[17] + 5] = (char)0xff; /* deallocated in BAM */ write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } if(result != TEST_UNRESOLVED) { result = TEST_PASS; if(image[2] != 2) { result = TEST_FAIL; } for(int b = 3; b < 256; b++) { if(image[b] != 0) { result = TEST_FAIL; } } if(result == TEST_PASS) { ++passed; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "BAM message for D64 should be limited to 85 chars"; ++test; if (run_binary_cleanup(binary, "-H 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17]+0xab] == '0' && image[track_offset[17]+255] == '4' && image[track_offset[17]+256] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "BAM message for SPEED DOS should be limited to 20 chars"; ++test; if (run_binary_cleanup(binary, "-4 -H 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (image[track_offset[17]+0xab] == '0' && image[track_offset[17]+0xab+19] == '9' && image[track_offset[17]+0xab+20] == 0) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "Two large and two single track transwarp files that just fit should leave track 21 empty"; ++test; create_value_file("1.prg", 8 * 21 * 223, 1); /* big file to fill everything until track 17 */ create_value_file("2.prg", 8 * 21 * 223, 2); /* big file to fill everything until track 17 */ create_value_file("3.prg", 19 * 223, 3); /* fits perfectly on track 20 */ create_value_file("4.prg", 21 * 223, 4); /* fits perfectly on track 17 */ if (run_binary_cleanup(binary, "-W 1.prg -W 2.prg -W 3.prg -W 4.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[20] / 256, 0)) { /* only check, if track 21 is free */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); remove("3.prg"); remove("4.prg"); description = "Lots of mixed files should just fit on disk"; ++test; create_value_file("1.prg", (5 * 19 + 18) * 223, 1); create_value_file("2.prg", 4 * 254, 2); create_value_file("3.prg", 17 * 254, 3); create_value_file("4.prg", (5 * 18 + 17) * 223, 4); create_value_file("5.prg", 12 * 21 * 223, 5); create_value_file("6.prg", 30 * 254, 6); create_value_file("7.prg", 2 * 254, 7); create_value_file("8.prg", 38 * 254, 8); create_value_file("9.prg", 4 * 21 * 223, 9); if (run_binary_cleanup(binary, "-W 1.prg -w 2.prg -w 3.prg -W 4.prg -W 5.prg -w 6.prg -w 7.prg -w 8.prg -W 9.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_FAIL; } else { result = TEST_PASS; /* passes if not unresolved */ ++passed; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); remove("3.prg"); remove("4.prg"); remove("5.prg"); remove("6.prg"); remove("7.prg"); remove("8.prg"); remove("9.prg"); description = "Transwarp allocation should avoid reserved track 10"; ++test; create_value_file("1.prg", 21 * 223, 1); create_value_file("2.prg", 8 * 21 * 223, 2); if (run_binary_cleanup(binary, "-r 10 -W 1.prg -W 2.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (!block_is_filled(image, track_offset[8] / 256, 0)) { /* only check that track 9 is not free */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Transwarp allocation should avoid reserved track 27"; ++test; create_value_file("1.prg", 8 * 21 * 223, 1); create_value_file("2.prg", 9 * 21 * 223, 1); create_value_file("3.prg", 18 * 223, 2); create_value_file("4.prg", 7 * 19 * 223, 2); if (run_binary_cleanup(binary, "-W 1.prg -W 2.prg -r 27 -W 3.prg -W 4.prg -w \"transwarp v0.86.prg\"", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_FAIL; } else if (block_is_filled(image, track_offset[19] / 256, 0)) { /* only check that track 20 is free */ result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); remove("3.prg"); remove("4.prg"); description = "-M width too low value should return error"; ++test; create_value_file("1.prg", 254, 1); if (run_binary_cleanup(binary, "-M 5 -f file1 -w 1.prg -f file2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-M 4 -f file1 -w 1.prg -f file2 -w 1.prg", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for wrong format specifier"; ++test; if (run_binary_cleanup(binary, "", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[track_offset[17]+2] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "-V should return error for wrong file type"; ++test; create_value_file("1.prg", 254, 1); if (run_binary(binary, "-f file -T 5 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for illegal track reference in directory"; ++test; create_value_file("1.prg", 254, 1); if (run_binary_cleanup(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[track_offset[17]+256+3] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for illegal sector reference in directory"; ++test; create_value_file("1.prg", 254, 1); if (run_binary_cleanup(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[track_offset[17]+256+4] = 21; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for illegal track reference in file chain"; ++test; create_value_file("1.prg", 2*254, 1); if (run_binary_cleanup(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[0] = 36; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for illegal sector reference in file chain"; ++test; create_value_file("1.prg", 2*254, 1); if (run_binary_cleanup(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[1] = 21; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for loop in file chain"; ++test; create_value_file("1.prg", 2*254, 1); if (run_binary_cleanup(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[1] = 0; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for collision in file chain"; ++test; create_value_file("1.prg", 2*254, 1); if (run_binary_cleanup(binary, "-f file1 -w 1.prg -r 2 -b 0 -f file2 -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[10*256+0] = 2; image[10*256+1] = 10; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "-V should return error for BAM being out of sync with files"; ++test; if (run_binary_cleanup(binary, "", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else { image[track_offset[17]+5] = (char)254; write_file("image.d64", size, image); if (run_binary_cleanup(binary, "-V", "image.d64", &image, &size, true) != NO_ERROR) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Patch should overwrite block 27"; ++test; create_value_file("1.prg", 256, 1); if (run_binary_cleanup(binary, "-p 2,6,0,1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 27, 1)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Patch should mark multiple blocks as used"; ++test; create_value_file("1.prg", 2*256, 1); if (run_binary_cleanup(binary, "-p 19,18,1,1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (((unsigned char)(image[track_offset[17] + 4 + 18*4 + 3]) == 3) && ((unsigned char)(image[track_offset[17] + 4 + 19*4 + 1]) == 252)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); description = "Writing file should avoid patched sector"; ++test; create_value_file("1.prg", 2*256, 1); create_value_file("2.prg", 1*254, 2); if (run_binary_cleanup(binary, "-p 1,0,0,1.prg -w 2.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, track_offset[0] / 256 + 2, 2)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("2.prg"); description = "Rewriting an extracted file should fill block 0"; ++test; create_value_file("1.prg", 1*256, 1); if (run_binary(binary, "-f file -w 1.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-X file", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (run_binary_cleanup(binary, "-w file.prg", "image.d64", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (block_is_filled(image, 1, 0)) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); remove("file.prg"); description = "New d71 image should have correct numbers of free blocks per track on track 18"; ++test; if (run_binary_cleanup(binary, "", "image.d71", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ( /* check only the first value for each zone */ ((unsigned char)(image[track_offset[17] + 0xdd]) == 0x15) && ((unsigned char)(image[track_offset[17] + 0xee]) == 0x00) && ((unsigned char)(image[track_offset[17] + 0xef]) == 0x13) && ((unsigned char)(image[track_offset[17] + 0xf5]) == 0x12) && ((unsigned char)(image[track_offset[17] + 0xfb]) == 0x11) ) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "New d71 image should not have free block numbers on track 53"; ++test; if (run_binary_cleanup(binary, "", "image.d71", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ( /* check only the first value for each zone */ ((unsigned char)(image[track_offset_b[17] + 0xdd]) == 0) && ((unsigned char)(image[track_offset_b[17] + 0xee]) == 0) && ((unsigned char)(image[track_offset_b[17] + 0xef]) == 0) && ((unsigned char)(image[track_offset_b[17] + 0xf5]) == 0) && ((unsigned char)(image[track_offset_b[17] + 0xfb]) == 0) ) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "New d71 image should have complete track 53 marked as used in BAM"; ++test; if (run_binary_cleanup(binary, "", "image.d71", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if ( ((unsigned char)(image[track_offset_b[17] + 0x33]) == 0) && ((unsigned char)(image[track_offset_b[17] + 0x34]) == 0) && ((unsigned char)(image[track_offset_b[17] + 0x35]) == 0) ) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); description = "File on second side should result in correct BAM and free block numbers for d71"; ++test; create_value_file("1.prg", 1*254, 1); if (run_binary_cleanup(binary, "-r 36 -b 0 -w 1.prg", "image.d71", &image, &size, false) != NO_ERROR) { result = TEST_UNRESOLVED; } else if (((unsigned char)(image[track_offset_b[17] + 0]) == 0xfe) && (((unsigned char)(image[track_offset[17] + 0xdd]) == 0x14))) { result = TEST_PASS; ++passed; } else { result = TEST_FAIL; } printf("%0*d: %s: %s\n", test_pad, test, result_str[result], description); remove("1.prg"); /* ideas for tests: - test writing of transwarp files - test encryption of transwarp files - check -g in more detail? */ /* clean up */ if (image != NULL) { free(image); } /* print summary */ printf("\nPassed %d of %d tests.\n", passed, test); if (passed == test) { return 0; } return 1; } cc1541-4.2/LICENSE.txt0000644000000000000000000000207714567574546012635 0ustar rootrootCopyright (c) 2008-2024 JackAsser, Krill, Claus, Björn Esser Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cc1541-4.2/test_cc1541.vcxproj0000644000000000000000000001643714567574546014373 0ustar rootroot Debug Win32 Release Win32 Debug x64 Release x64 {4206D44B-8628-4E98-9F6F-775C8AC5E1B9} Win32Proj test_cc1541 10.0 Application true v142 Unicode Application false v142 true Unicode Application true v142 Unicode Application false v142 true Unicode true true $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(ProjectName)\$(Configuration)\ false false $(Platform)\$(ProjectName)\$(Configuration)\ Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 MaxSpeed true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true Level3 MaxSpeed true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true cc1541-4.2/cc1541.vcxproj.filters0000644000000000000000000000167414567574546015000 0ustar rootroot {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Source Files cc1541-4.2/cc1541.1.txt.in0000644000000000000000000001463214567574546013217 0ustar rootroot= cc1541(1) Björn Esser :doctype: manpage :Author: JackAsser, Krill, Claus, Björn Esser :Email: besser82@fedoraproject.org :manvolnum: 1 :manversion: v@@VERSION@@ :manmanual: cc1541 manual :mansource: cc1541 :man-linkstyle: pass:[blue R < >] == Name cc1541 - A tool for creating Commodore floppy disk images in D64 (35 and 40 track), D71 or D81 format, providing a lot of control over the layout of the data on the disk. It also supports writing the file format for the Transwarp loader by Krill. Find a more extensive online documentation here: https://acoustic-velocity.com/cc1541 == Synopsis *cc1541* [_options_] image.[_d64|d71|d81_] == Options === Disk Related *-n diskname*:: Disk name, default='cc1541'. *-i id*:: Disk ID, default='00 2a'. *-4*:: Use tracks 35-40 with SPEED DOS BAM formatting. *-5*:: Use tracks 35-40 with DOLPHIN DOS BAM formatting. *-d track*:: Maintain a shadow directory (copy of the actual directory without a valid BAM). *-g filename*:: Write additional g64 output file with given name. *-H message*:: Hidden BAM message. Only for D64 (up to 85 chars) or SPEED DOS (up to 20 chars). *-p t,s,o,file*:: Write binary patch from file to track t, sector s, offset o. *-V*:: Do not modify image unless it is in valid CBM DOS format. === File Related *-w localname*:: Write local file to disk, if filename is not set then the local name is used. After file written, the filename is unset. *-f filename*:: Use filename as name when writing next file, use prefix # to include arbitrary PETSCII characters (e.g. -f "start#a0,8,1"). *-T filetype*:: Filetype for next file, allowed parameters are PRG, SEQ, USR, REL and DEL, or a decimal number between 0 and 255. Default is PRG. *-P*:: Set write protect flag for next file. *-O*:: Set open flag for next file. *-B numblocks*:: Write the given value as file size in blocks to the directory for the next file. Not applicable for Transwarp files. *-L*:: Add dir entry without writing file (track and sector will be 0), requires a filename given with -f. *-l filename*:: Write loop file (an additional dir entry) to existing file to disk. Wildcards * and ? are supported. Set filename with -f. *-N*:: Force creation of a new directory entry, even if a file with the same name exists already. *-o*:: Do not overwrite if file with same name exists already. *-X filename*:: Extract file from disk image. Wildcards * and ? are supported. === Track/Sector Control *-S value*:: Default sector interleave, default=10. Not applicable for D81. *-s value*:: Next file sector interleave, valid after each file. The interleave value falls back to the default value set by -S after the first sector of the next file. Not applicable for D81 or Transwarp files. *-e*:: Start next file on an empty track (default start sector is current sector plus interleave). *-E*:: Try to fit file on a single track. *-r track*:: Restrict next file blocks to the specified track or higher. *-b sector*:: Set next file beginning sector to the specified value. Not applicable for D81 or Transwarp files. *-F value*:: Next file first sector on a new track (default=0). Any negative value assumes aligned tracks and uses current sector + interleave - value. After each file, the value falls back to the default. Not applicable for D81. *-t*:: Use directory track to also store files (makes -x useless) (default no). *-u numblocks*:: When using -t, amount of dir blocks to leave free (default=2). *-x*:: Don't split files over directory track hole (default split files). *-c*:: Save next file cluster-optimized (d71 only). === Fastloader Specific *-M numchars*:: Hash computation maximum filename length, this must match loader option FILENAME_MAXLENGTH in Krill's loader. Default is 16. *-m*:: Ignore filename hash collisions, without this switch a collision results in an error. *-W localname*:: Like -w, but encode file in Transwarp format. Provide Transwarp bootfile as last file using "-f 'transwarp vX.YZ' -w 'transwarp vX.YZ.prg'" *-A value*:: Select allocation strategy for Transwarp files (default 1). 0: allocate in the order given on the commandline. 1: save disk space by reordering transwarp files. *-K key*:: Set an encryption key for Transwarp files, a string of up to 29 characters. === Miscellaneous *-h*:: Print this command line help. *-v*:: Be verbose. *-q*:: Be quiet. *-U mapping*:: Print PETSCII as Unicode (requires Unicode 13.0 font, e.g. UNSCII). Use mapping 0 for ASCII output, 1 for upper case, 2 for lower case, default is 0. *-R level*:: Try to restore deleted and formatted files. level 0: Only restore dir entries without touching any t/s links. level 1: Fix dir entries for files with valid t/s chains. level 2: Also add wild sector chains with valid t/s chains. level 3: Also fix dir entries with invalid t/s chains. level 4: Also add and fix wild invalid t/s chains. level 5: Also add reasonable wild single blocks. *-a*:: Print command line options that would create the same directory as the one in the given image (for directory art import). == Exit status *0*:: Success. *-1*:: Failure. == Resources Project web site: https://bitbucket.org/PTV_Claus/cc1541/src == Bug Reports Please report bugs to: https://bitbucket.org/PTV_Claus/cc1541/issues/new == License Copyright (C) 2008-2021 {author}. + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.