diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a7b09930 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +all: + cd Tools/unix ; make install + cd Source ; make all + +clean: + cd Tools/unix ; make clean + cd Source ; make clean + cd Binary ; make clean + +clobber: + cd Tools/unix ; make clobber + cd Source ; make clobber + cd Binary ; make clobber + +diff: + cd Source ; make diff + diff --git a/Readme.docker b/Readme.docker new file mode 100644 index 00000000..7c5b752a --- /dev/null +++ b/Readme.docker @@ -0,0 +1,12 @@ +my mac has a case-insensitive filesystem, so I built a case sensitive one and mounted it as /Volumes/sensitive. + +create a docker instance and put tools into it + +docker run -d --name ubuntu -v /Volumes/IronKey:/sensitive -ti ubuntu /bin/bash +docker exec ubuntu /bin/bash -c 'apt update ; apt-get install -y make gcc' +docker attach ubuntu + +inside it: + + cd /sensitive/src/RomWBW + make diff --git a/Readme.unix b/Readme.unix new file mode 100644 index 00000000..fcc12ca3 --- /dev/null +++ b/Readme.unix @@ -0,0 +1,7 @@ +prerequisites: + +gcc +yacc +make +g++ + diff --git a/Source/Apps/Makefile b/Source/Apps/Makefile new file mode 100644 index 00000000..fcf3a5d6 --- /dev/null +++ b/Source/Apps/Makefile @@ -0,0 +1,23 @@ +OBJECTS = SysGen.com Survey.com \ + SysCopy.COM Assign.COM Format.COM Talk.COM OSLdr.COM Mode.COM RTC.COM \ + Timer.COM IntTest.COM +OTHERS = *.hex *.com +SUBDIRS = XM FDU Tune FAT +DEST = ../../Binary/Apps +TOOLS =../../Tools +RELPATH = Source/Apps + +include $(TOOLS)/Makefile.inc + +%.COM: %.asm + $(TASM) $< $@ + +foo: + echo treeroot: $(TREEROOT) here: $(HERE) relpath: $(RELPATH1) + +#diff:: +# -for i in $(OBJECTS) ; do \ +# sf=$$($(CASEFN) $$i) ; df=$$($(CASEFN) $(DIFFTO)/$(RELPATH)/$$i) ; \ +# echo diffing $$sf and $$df ; \ +# diff $$sf $$df ; \ +# done diff --git a/Source/BPBIOS/Makefile b/Source/BPBIOS/Makefile new file mode 100644 index 00000000..9c5ad518 --- /dev/null +++ b/Source/BPBIOS/Makefile @@ -0,0 +1,90 @@ +VERSIONS = \ + 33t 33tbnk \ + 33n 33nbnk \ + 34t 34tbnk \ + 34n 34nbnk \ + 41tbnk 41nbnk + +OBJECTS = $(foreach ver,$(VERSIONS),bp$(ver).img) +OTHERS = zcpr33n.rel zcpr33t.rel bpbio-ww.rel bpsys.dat bpsys.bak +TOOLS = ../../Tools +CPMCP = $(TOOLS)/`uname`/cpmcp + +SUBDIRS = ZCPR33 +include $(TOOLS)/Makefile.inc + +zcpr33n.rel zcpr33t.rel: + (cd $ZCPR33 ; make install) + +all:: $(OBJECTS) + $(CPMCP) -f wbw_hd0 ../../Binary/hd0.img *.img *.rel *.zex myterm.z3t 0: + +%.img: + $(eval VER := $(subst .img,,$(subst bp,,$@))) + cp def-ww-z$(VER).lib def-ww.lib + rm -f bpbio-ww.rel + $(ZXCC) $(CPM)/ZMAC -BPBIO-WW -/P + mv bpbio-ww.prn bp$(VER).prn + cp bp$(VER).dat bpsys.dat + $(ZXCC) ./bpbuild.com -bpsys.dat 0 < bpbld1.rsp + cp bpsys.img bpsys.dat + $(ZXCC) ./bpbuild.com -bpsys.dat 0 < bpbld2.rsp + mv bpsys.img bp$(VER).img + +# +# +# rem cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:*.dat +# rem cpmcp.exe -f wbw_hd0 ../../Binary/hd0.img *.dat 0: +# +# cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:*.zex +# +# cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:myterm.z3t +# +# goto :eof +# +# :makebp +# +# set VER=%1 +# echo. +# echo Building BPBIOS Variant "%VER%"... +# echo. +# +# copy def-ww-z%VER%.lib def-ww.lib +# rem if exist bpbio-ww.rel del bpbio-ww.rel +# zx ZMAC -BPBIO-WW -/P +# if exist bp%VER%.prn del bp%VER%.prn +# ren bpbio-ww.prn bp%VER%.prn +# +# rem pause +# +# rem BPBUILD attempts to rename bpsys.img -> bpsys.bak +# rem while is is still open. Real CP/M does not care, +# rem but zx fails due to host OS. Below, a temp file +# rem is used to avoid the problematic rename. +# +# if exist bpsys.img del bpsys.img +# if exist bpsys.tmp del bpsys.tmp +# copy bp%VER%.dat bpsys.tmp +# rem bpsys.tmp -> bpsys.img +# zx bpbuild -bpsys.tmp bpsys.img +# zx bpbuild -bpsys.tmp + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/Tools/unix/cpmtools/Makefile b/Tools/unix/cpmtools/Makefile new file mode 100644 index 00000000..0d83e0ad --- /dev/null +++ b/Tools/unix/cpmtools/Makefile @@ -0,0 +1,57 @@ +# +# cpmtools makefile stripped down to remove autoconf +# + +UNAME := $(shell uname) +DEST = ../../$(UNAME) + +CC = gcc +CFLAGS = -g + +DEFFORMAT = ibm-3740 +DEVICE = posix +CPPFLAGS = -DDISKDEFS=\"$(DISKDEFS)\" -DFORMAT=\"$(DEFFORMAT)\" + +DEVICEOBJ = device_posix.o + +OBJECTS = cpmls cpmrm cpmcp cpmchmod cpmchattr mkfs.cpm fsck.cpm + +all: $(OBJECTS) + +cpmls: cpmls.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmls.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmrm: cpmrm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmrm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmcp: cpmcp.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmcp.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmchmod: cpmchmod.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmchmod.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmchattr: cpmchattr.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmchattr.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +mkfs.cpm: mkfs.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ mkfs.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +fsck.cpm: fsck.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ fsck.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +fsed.cpm: fsed.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ fsed.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +$(DEST): + mkdir -p $(DEST) + +install: all $(DEST) + cp $(OBJECTS) $(DEST) + +clean: + -rm -f *.o $(OBJECTS) + +clobber: clean + -for i in $(OBJECTS) ; do \ + rm -f $(DEST)/$$i ; \ + done diff --git a/Tools/unix/cpmtools/badfs/Makefile b/Tools/unix/cpmtools/badfs/Makefile new file mode 100644 index 00000000..75c4ee00 --- /dev/null +++ b/Tools/unix/cpmtools/badfs/Makefile @@ -0,0 +1,7 @@ +# +# Dummy makefile +# +all: +clean: +distclean: + diff --git a/Tools/unix/cpmtools/badfs/blocknumber b/Tools/unix/cpmtools/badfs/blocknumber new file mode 100644 index 00000000..9259cacb Binary files /dev/null and b/Tools/unix/cpmtools/badfs/blocknumber differ diff --git a/Tools/unix/cpmtools/badfs/doubleext b/Tools/unix/cpmtools/badfs/doubleext new file mode 100644 index 00000000..8ee7d65b Binary files /dev/null and b/Tools/unix/cpmtools/badfs/doubleext differ diff --git a/Tools/unix/cpmtools/badfs/extension b/Tools/unix/cpmtools/badfs/extension new file mode 100644 index 00000000..cc02845f Binary files /dev/null and b/Tools/unix/cpmtools/badfs/extension differ diff --git a/Tools/unix/cpmtools/badfs/extno b/Tools/unix/cpmtools/badfs/extno new file mode 100644 index 00000000..5948b0ee Binary files /dev/null and b/Tools/unix/cpmtools/badfs/extno differ diff --git a/Tools/unix/cpmtools/badfs/hugecom b/Tools/unix/cpmtools/badfs/hugecom new file mode 100644 index 00000000..3d8fea49 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/hugecom differ diff --git a/Tools/unix/cpmtools/badfs/label b/Tools/unix/cpmtools/badfs/label new file mode 100644 index 00000000..982928d1 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/label differ diff --git a/Tools/unix/cpmtools/badfs/lcr b/Tools/unix/cpmtools/badfs/lcr new file mode 100644 index 00000000..da20f4b5 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/lcr differ diff --git a/Tools/unix/cpmtools/badfs/multipleblocks b/Tools/unix/cpmtools/badfs/multipleblocks new file mode 100644 index 00000000..52c1d922 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/multipleblocks differ diff --git a/Tools/unix/cpmtools/badfs/name b/Tools/unix/cpmtools/badfs/name new file mode 100644 index 00000000..67265aca Binary files /dev/null and b/Tools/unix/cpmtools/badfs/name differ diff --git a/Tools/unix/cpmtools/badfs/recordcount b/Tools/unix/cpmtools/badfs/recordcount new file mode 100644 index 00000000..e8633a56 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/recordcount differ diff --git a/Tools/unix/cpmtools/badfs/status b/Tools/unix/cpmtools/badfs/status new file mode 100644 index 00000000..33da2481 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/status differ diff --git a/Tools/unix/cpmtools/badfs/timestamps b/Tools/unix/cpmtools/badfs/timestamps new file mode 100644 index 00000000..a434cc96 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/timestamps differ diff --git a/Tools/unix/cpmtools/config.h b/Tools/unix/cpmtools/config.h new file mode 100644 index 00000000..2b5cd544 --- /dev/null +++ b/Tools/unix/cpmtools/config.h @@ -0,0 +1,57 @@ +/* config.h. Generated from config.h.in by configure. */ +#define HAVE_FCNTL_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_WINDOWS_H 0 +#define HAVE_WINIOCTL_H 0 +#define HAVE_LIBDSK_H 0 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_MODE_T 0 +#define NEED_NCURSES 0 +#define HAVE_NCURSES_NCURSES_H 0 + +#if HAVE_SYS_STAT_H +#include +#endif + +#if HAVE_SYS_TYPES_H +#include +#endif + +#if HAVE_LIMITS_H +#include +#endif + +#if HAVE_UNISTD_H +#include +#endif + +#if HAVE_WINDOWS_H +#include +#endif + +#if HAVE_WINIOCTL_H +#include +#endif + +#if HAVE_LIBDSK_H +#include +#endif + +#if HAVE_FCNTL_H +#include +#endif + +#ifndef _POSIX_PATH_MAX +#define _POSIX_PATH_MAX _MAX_PATH +#endif + +#include + +/* Define either for large file support, if your OS needs them. */ +/* #undef _FILE_OFFSET_BITS */ +/* #undef _LARGE_FILES */ + +/* Define if using dmalloc */ +/* #undef USE_DMALLOC */ diff --git a/Tools/unix/cpmtools/cpm.5 b/Tools/unix/cpmtools/cpm.5 new file mode 100644 index 00000000..9f11ff98 --- /dev/null +++ b/Tools/unix/cpmtools/cpm.5 @@ -0,0 +1,300 @@ +.\" Believe it or not, reportedly there are nroffs which do not know \(en +.if n .ds en - +.if t .ds en \(en +.TH CPM 5 "October 25, 2014" "CP/M tools" "File formats" +.SH NAME \"{{{roff}}}\"{{{ +cpm \- CP/M disk and file system format +.\"}}} +.SH DESCRIPTION \"{{{ +.SS "Characteristic sizes" \"{{{ +Each CP/M disk format is described by the following specific sizes: +.RS +.sp +Sector size in bytes +.br +Number of tracks +.br +Number of sectors +.br +Block size +.br +Number of directory entries +.br +Logical sector skew +.br +Number of reserved system tracks (optional) +.br +Offset to start of volume (optional and not covered by operating system, +but disk driver specific) +.sp +.RE +A block is the smallest allocatable storage unit. CP/M supports block +sizes of 1024, 2048, 4096, 8192 and 16384 bytes. Unfortunately, this +format specification is not stored on the disk and there are lots of +formats. Accessing a block is performed by accessing its sectors, which +are stored with the given software skew. +.\"}}} +.SS "Device areas" \"{{{ +A CP/M disk contains four areas: +.RS +.sp +Volume offset (optional and not covered by operating system, but disk driver specific) +.br +System tracks (optional) +.br +Directory +.br +Data +.sp +.RE +The system tracks store the boot loader and CP/M itself. In order to save +disk space, there are non-bootable formats which omit those system tracks. +The term \fIdisk capacity\fP always excludes the space for system tracks. +Note that there is no bitmap or list for free blocks. When accessing a +drive for the first time, CP/M builds this bitmap in core from the directory. +.LP +A hard disk can have the additional notion of a \fIvolume offset\fP to +locate the start of the drive image (which may or may not have system +tracks associated with it). The base unit for volume offset is byte +count from the beginning of the physical disk, but specifiers of +\fIK\fP, \fIM\fP, \fIT\fP or \fIS\fP may be appended to denote +kilobytes, megabytes, tracks or sectors. If provided, a specifier +must immediately follow the numeric value with no whitespace. For +convenience upper and lower case are both accepted and only the first +letter is significant, thus 2KB, 8MB, 1000trk and 16sec are valid +values. The \fBoffset\fP must appear subsequent to track, sector and sector +length values for the sector and track units to work. +.\"}}} +.SS "Directory entries" \"{{{ +The directory is a sequence of directory entries (also called extents), +which contain 32 bytes of the following structure: +.RS +.sp +.ta 3n 6n 9n 12n 15n 18n 21n 24n 27n 30n 33n 36n 39n 42n 45n +St F0 F1 F2 F3 F4 F5 F6 F7 E0 E1 E2 Xl Bc Xh Rc +.br +Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al +.sp +.RE +.\"{{{ St = status +\fBSt\fP is the status; possible values are: +.RS +.sp +0\*(en15: used for file, status is the user number +.br +16\*(en31: used for file, status is the user number (P2DOS) +or used for password extent (CP/M 3 or higher) +.br +32: disc label +.br +33: time stamp (P2DOS) +.br +0xE5: unused +.sp +.RE +.\"}}} +.LP +.\"{{{ F0-E2 = file name and extension +\fBF0\*(enE2\fP are the file name and its extension. They may consist of +any printable 7 bit ASCII character but: \fB< > . , ; : = ? * [ ]\fP. +The file name must not be empty, the extension may be empty. Both are +padded with blanks. The highest bit of each character of the file name +and extension is used as attribute. The attributes have the following +meaning: +.RS +.sp +F0: requires set wheel byte (Backgrounder II) +.br +F1: public file (P2DOS, ZSDOS), forground-only command (Backgrounder II) +.br +F2: date stamp (ZSDOS), background-only commands (Backgrounder II) +.br +F7: wheel protect (ZSDOS) +.br +E0: read-only +.br +E1: system file +.br +E2: archived +.sp +.RE +Public files (visible under each user number) are not supported by CP/M +2.2, but there is a patch and some free CP/M clones support them without +any patches. +.LP +The wheel byte is (by default) the memory location at 0x4b. If it is +zero, only non-privileged commands may be executed. +.\"}}} +.LP +.\"{{{ Xl, Xh = extent number +\fBXl\fP and \fBXh\fP store the extent number. A file may use more than +one directory entry, if it contains more blocks than an extent can hold. +In this case, more extents are allocated and each of them is numbered +sequentially with an extent number. If a physical extent stores more than +16k, it is considered to contain multiple logical extents, each pointing +to 16k data, and the extent number of the last used logical extent +is stored. Note: Some formats decided to always store only one logical +extent in a physical extent, thus wasting extent space. CP/M 2.2 allows +512 extents per file, CP/M 3 and higher allow up to 2048. Bit 5\*(en7 of +Xl are 0, bit 0\*(en4 store the lower bits of the extent number. Bit 6 +and 7 of Xh are 0, bit 0\*(en5 store the higher bits of the extent number. +.\"}}} +.LP +.\"{{{ Rc, Bc = record count, byte count +\fBRc\fP and \fBBc\fP determine the length of the data used by this extent. The +physical extent is divided into logical extents, each of them being 16k +in size (a physical extent must hold at least one logical extent, e.g. a +blocksize of 1024 byte with two-byte block pointers is not allowed). +Rc stores the number of 128 byte records of the last used logical extent. +Bc stores the number of bytes in the last used record. The value 0 means +128 for backward compatibility with CP/M 2.2, which did not support Bc. +ISX records the number of unused instead of used bytes in Bc. +.\"}}} +.LP +.\"{{{ Al = allocated blocks +\fBAl\fP stores block pointers. If the disk capacity minus boot +tracks but including the directory area is less than 256 blocks, Al +is interpreted as 16 byte-values, otherwise as 8 double-byte-values. +Since the directory area is not subtracted, the directory area starts +with block 0 and files can never allocate block 0, which is why this +value can be given a new meaning: A block pointer of 0 marks a hole in +the file. If a hole covers the range of a full extent, the extent will +not be allocated. In particular, the first extent of a file does not +neccessarily have extent number 0. A file may not share blocks with other +files, as its blocks would be freed if the other files is erased without +a following disk system reset. CP/M returns EOF when it reaches a hole, +whereas UNIX returns zero-value bytes, which makes holes invisible. +.\"}}} +.\"}}} +.SS "Native time stamps" \"{{{ +P2DOS and CP/M Plus support time stamps, which are stored in each fourth +directory entry. This entry contains the time stamps for +the extents using the previous three directory entries. Note that you +really have time stamps for each extent, no matter if it is the first +extent of a file or not. The structure of time stamp entries is: +.RS +.sp +1 byte status 0x21 +.br +8 bytes time stamp for third-last directory entry +.br +2 bytes unused +.br +8 bytes time stamp for second-last directory entry +.br +2 bytes unused +.br +8 bytes time stamp for last directory entry +.sp +.RE +A time stamp consists of two dates: Creation and modification date (the +latter being recorded when the file is closed). CP/M Plus further +allows optionally to record the access instead of creation date as first +time stamp. +.RS +.sp +2 bytes (little-endian) days starting with 1 at 01-01-1978 +.br +1 byte hour in BCD format +.br +1 byte minute in BCD format +.sp +.RE +All time stamps are stored in local time. +.\"}}} +.SS "DateStamper time stamps" \"{{{ +The DateStamper software added functions to the BDOS to manage +time stamps by allocating a read only file with the name "!!!TIME&.DAT" +in the very first directory entry, covering the very first data +blocks. It contains one entry per directory entry with the +following structure of 16 bytes: +.RS +.sp +5 bytes create datefield +.br +5 bytes access datefield +.br +5 bytes modify datefield +.br +1 byte magic number/checksum +.sp +.RE +The magic number is used for the first 7 entries of each 128-byte record +and contains the characters \fB!\fP, \fB!\fP, \fB!\fP, \fBT\fP, \fBI\fP, +\fBM\fP and \fBE\fP. The checksum is used on every 8th entry (last entry +in 128-byte record) and is the sum of the first 127 bytes of the record. +Each datefield has this structure: +.RS +.sp +1 byte BCD coded year (no century, so it is sane assuming any year < 70 +means 21st century) +.br +1 byte BCD coded month +.br +1 byte BCD coded day +.br +1 byte BCD coded hour or, if the high bit is set, the high byte of a +counter for systems without real time clock +.br +1 byte BCD coded minute, or the low byte of the counter +.sp +.DE +.\"}}} +.SS "Disc labels" \"{{{ +CP/M Plus support disc labels, which are stored in an arbitrary directory +entry. +The structure of disc labels is: +.RS +.sp +1 byte status 0x20 +.br +\fBF0\*(enE2\fP are the disc label +.br +1 byte mode: bit 7 activates password protection, bit 6 causes time stamps on +access, but 5 causes time stamps on modifications, bit 4 causes time stamps on +creation and bit 0 is set when a label exists. Bit 4 and 6 are exclusively set. +.br +1 byte password decode byte: To decode the password, xor this byte with the password +bytes in reverse order. To encode a password, add its characters to get the +decode byte. +.br +2 reserved bytes +.br +8 password bytes +.br +4 bytes label creation time stamp +.br +4 bytes label modification time stamp +.sp +.RE +.\"}}} +.SS "Passwords" \"{{{ +CP/M Plus supports passwords, which are stored in an arbitrary directory +entry. +The structure of these entries is: +.RS +.sp +1 byte status (user number plus 16) +.br +\fBF0\*(enE2\fP are the file name and its extension. +.br +1 byte password mode: bit 7 means password required for reading, bit 6 for writing +and bit 5 for deleting. +.br +1 byte password decode byte: To decode the password, xor this byte with the password +bytes in reverse order. To encode a password, add its characters to get the +decode byte. +.br +2 reserved bytes +.br +8 password bytes +.sp +.RE +.\"}}} +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR mkfs.cpm (1), +.IR fsck.cpm (1), +.IR fsed.cpm (1), +.IR cpmls (1) +.\"}}} diff --git a/Tools/unix/cpmtools/cpm.ps b/Tools/unix/cpmtools/cpm.ps new file mode 100644 index 00000000..6c631338 --- /dev/null +++ b/Tools/unix/cpmtools/cpm.ps @@ -0,0 +1,478 @@ +%!PS-Adobe-3.0 +%%Creator: groff version 1.19 +%%CreationDate: Sun Feb 3 19:48:55 2013 +%%DocumentNeededResources: font Times-Roman +%%+ font Times-Bold +%%+ font Times-Italic +%%DocumentSuppliedResources: procset grops 1.19 0 +%%Pages: 4 +%%PageOrder: Ascend +%%DocumentMedia: Default 595 842 0 () () +%%Orientation: Portrait +%%EndComments +%%BeginDefaults +%%PageMedia: Default +%%EndDefaults +%%BeginProlog +%%BeginResource: procset grops 1.19 0 +/setpacking where{ +pop +currentpacking +true setpacking +}if +/grops 120 dict dup begin +/SC 32 def +/A/show load def +/B{0 SC 3 -1 roll widthshow}bind def +/C{0 exch ashow}bind def +/D{0 exch 0 SC 5 2 roll awidthshow}bind def +/E{0 rmoveto show}bind def +/F{0 rmoveto 0 SC 3 -1 roll widthshow}bind def +/G{0 rmoveto 0 exch ashow}bind def +/H{0 rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/I{0 exch rmoveto show}bind def +/J{0 exch rmoveto 0 SC 3 -1 roll widthshow}bind def +/K{0 exch rmoveto 0 exch ashow}bind def +/L{0 exch rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/M{rmoveto show}bind def +/N{rmoveto 0 SC 3 -1 roll widthshow}bind def +/O{rmoveto 0 exch ashow}bind def +/P{rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/Q{moveto show}bind def +/R{moveto 0 SC 3 -1 roll widthshow}bind def +/S{moveto 0 exch ashow}bind def +/T{moveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/SF{ +findfont exch +[exch dup 0 exch 0 exch neg 0 0]makefont +dup setfont +[exch/setfont cvx]cvx bind def +}bind def +/MF{ +findfont +[5 2 roll +0 3 1 roll +neg 0 0]makefont +dup setfont +[exch/setfont cvx]cvx bind def +}bind def +/level0 0 def +/RES 0 def +/PL 0 def +/LS 0 def +/MANUAL{ +statusdict begin/manualfeed true store end +}bind def +/PLG{ +gsave newpath clippath pathbbox grestore +exch pop add exch pop +}bind def +/BP{ +/level0 save def +1 setlinecap +1 setlinejoin +72 RES div dup scale +LS{ +90 rotate +}{ +0 PL translate +}ifelse +1 -1 scale +}bind def +/EP{ +level0 restore +showpage +}bind def +/DA{ +newpath arcn stroke +}bind def +/SN{ +transform +.25 sub exch .25 sub exch +round .25 add exch round .25 add exch +itransform +}bind def +/DL{ +SN +moveto +SN +lineto stroke +}bind def +/DC{ +newpath 0 360 arc closepath +}bind def +/TM matrix def +/DE{ +TM currentmatrix pop +translate scale newpath 0 0 .5 0 360 arc closepath +TM setmatrix +}bind def +/RC/rcurveto load def +/RL/rlineto load def +/ST/stroke load def +/MT/moveto load def +/CL/closepath load def +/Fr{ +setrgbcolor fill +}bind def +/setcmykcolor where{ +pop +/Fk{ +setcmykcolor fill +}bind def +}if +/Fg{ +setgray fill +}bind def +/FL/fill load def +/LW/setlinewidth load def +/Cr/setrgbcolor load def +/setcmykcolor where{ +pop +/Ck/setcmykcolor load def +}if +/Cg/setgray load def +/RE{ +findfont +dup maxlength 1 index/FontName known not{1 add}if dict begin +{ +1 index/FID ne{def}{pop pop}ifelse +}forall +/Encoding exch def +dup/FontName exch def +currentdict end definefont pop +}bind def +/DEFS 0 def +/EBEGIN{ +moveto +DEFS begin +}bind def +/EEND/end load def +/CNT 0 def +/level1 0 def +/PBEGIN{ +/level1 save def +translate +div 3 1 roll div exch scale +neg exch neg exch translate +0 setgray +0 setlinecap +1 setlinewidth +0 setlinejoin +10 setmiterlimit +[]0 setdash +/setstrokeadjust where{ +pop +false setstrokeadjust +}if +/setoverprint where{ +pop +false setoverprint +}if +newpath +/CNT countdictstack def +userdict begin +/showpage{}def +/setpagedevice{}def +}bind def +/PEND{ +clear +countdictstack CNT sub{end}repeat +level1 restore +}bind def +end def +/setpacking where{ +pop +setpacking +}if +%%EndResource +%%BeginFeature: *PageSize Default +<< /PageSize [ 595 842 ] /ImagingBBox null >> setpagedevice +%%EndFeature +%%IncludeResource: font Times-Roman +%%IncludeResource: font Times-Bold +%%IncludeResource: font Times-Italic +grops begin/DEFS 1 dict def DEFS begin/u{.001 mul}bind def end/RES 72 +def/PL 841.89 def/LS false def/ENC0[/asciicircum/asciitilde/Scaron +/Zcaron/scaron/zcaron/Ydieresis/trademark/quotesingle/Euro/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/space/exclam/quotedbl/numbersign/dollar/percent +/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen +/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon +/semicolon/less/equal/greater/question/at/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/bracketleft/backslash/bracketright/circumflex +/underscore/quoteleft/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/braceleft/bar/braceright/tilde/.notdef/quotesinglbase/guillemotleft +/guillemotright/bullet/florin/fraction/perthousand/dagger/daggerdbl +/endash/emdash/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/hungarumlaut +/dotaccent/breve/caron/ring/ogonek/quotedblleft/quotedblright/oe/lslash +/quotedblbase/OE/Lslash/.notdef/exclamdown/cent/sterling/currency/yen +/brokenbar/section/dieresis/copyright/ordfeminine/guilsinglleft +/logicalnot/minus/registered/macron/degree/plusminus/twosuperior +/threesuperior/acute/mu/paragraph/periodcentered/cedilla/onesuperior +/ordmasculine/guilsinglright/onequarter/onehalf/threequarters +/questiondown/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE +/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex +/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis +/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn +/germandbls/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla +/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis +/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash +/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def +/Times-Italic@0 ENC0/Times-Italic RE/Times-Bold@0 ENC0/Times-Bold RE +/Times-Roman@0 ENC0/Times-Roman RE +%%EndProlog +%%Page: 1 1 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F/F1 10.95/Times-Bold@0 SF -.219(NA)72 84 S(ME) +.219 E F0(cpm \255 CP/M disk and \214le system format)108 96 Q F1 +(DESCRIPTION)72 112.8 Q/F2 10/Times-Bold@0 SF(Characteristic sizes)87 +124.8 Q F0(Each CP/M disk format is described by the follo)108 136.8 Q +(wing speci\214c sizes:)-.25 E(Sector size in bytes)144 160.8 Q +(Number of tracks)144 172.8 Q(Number of sectors)144 184.8 Q(Block size) +144 196.8 Q(Number of directory entries)144 208.8 Q(Logical sector sk) +144 220.8 Q -.25(ew)-.1 G(Number of reserv)144 232.8 Q +(ed system tracks \(optional\))-.15 E(Of)144 244.8 Q(fset to start of v) +-.25 E(olume \(optional\))-.2 E 2.848(Ab)108 268.8 S .348 +(lock is the smallest allocatable storage unit.)-2.848 F .347 +(CP/M supports block sizes of 1024, 2048, 4096, 8192 and)5.348 F .207 +(16384 bytes.)108 280.8 R(Unfortunately)5.207 E 2.707(,t)-.65 G .208(hi\ +s format speci\214cation is not stored on the disk and there are lots o\ +f formats.)-2.707 F(Accessing a block is performed by accessing its sec\ +tors, which are stored with the gi)108 292.8 Q -.15(ve)-.25 G 2.5(ns).15 +G(oftw)-2.5 E(are sk)-.1 E -.25(ew)-.1 G(.)-.4 E F2(De)87 309.6 Q +(vice ar)-.15 E(eas)-.18 E F0 2.5(AC)108 321.6 S +(P/M disk contains three areas:)-2.5 E -1.29(Vo)144 345.6 S(lume of)1.29 +E(fset \(optional\))-.25 E(System tracks \(optional\))144 357.6 Q +(Directory)144 369.6 Q(Data)144 381.6 Q .058 +(The system tracks store the boot loader and CP/M itself.)108 405.6 R +.058(In order to sa)5.058 F .358 -.15(ve d)-.2 H .057 +(isk space, there are non-bootable).15 F 1.55 +(formats which omit those system tracks.)108 417.6 R 1.55(The term)6.55 +F/F3 10/Times-Italic@0 SF 1.55(disk capacity)4.05 F F0(al)4.05 E -.1(wa) +-.1 G 1.55(ys e).1 F 1.55(xcludes the space for system)-.15 F 2.748 +(tracks. Note)108 429.6 R .248 +(that there is no bitmap or list for free blocks.)2.748 F .248 +(When accessing a dri)5.248 F .548 -.15(ve f)-.25 H .248 +(or the \214rst time, CP/M).15 F -.2(bu)108 441.6 S +(ilds this bitmap in core from the directory).2 E(.)-.65 E 3.15(Ah)108 +458.4 S .65(ard disk can ha)-3.15 F .95 -.15(ve t)-.2 H .65 +(he additional notion of a).15 F F3 .65(volume of)3.15 F(fset)-.18 E F0 +.65(to locate the start of the dri)3.15 F .95 -.15(ve i)-.25 H .65 +(mage \(which).15 F .531(may or may not ha)108 470.4 R .831 -.15(ve s) +-.2 H .531(ystem tracks associated with it\). The base unit for v).15 F +.53(olume of)-.2 F .53(fset is byte count from)-.25 F 1.224(the be)108 +482.4 R 1.224(ginning of the ph)-.15 F 1.224(ysical disk, b)-.05 F 1.225 +(ut speci\214ers of)-.2 F F3(K)3.725 E F0(,)A F3(M)3.725 E F0(,)A F3(T) +3.725 E F0(or)3.725 E F3(S)3.725 E F0 1.225 +(may be appended to denote kilobytes,)3.725 F(me)108 494.4 Q -.05(ga) +-.15 G .806(bytes, tracks or sectors.).05 F .806(If pro)5.806 F .805 +(vided, a speci\214er must immediately follo)-.15 F 3.305(wt)-.25 G .805 +(he numeric v)-3.305 F .805(alue with no)-.25 F 2.881(whitespace. F)108 +506.4 R .381(or con)-.15 F -.15(ve)-.4 G .381(nience upper and lo).15 F +.381(wer case are both accepted and only the \214rst letter is signi\ +\214cant,)-.25 F .02(thus 2KB, 8MB, 1000trk and 16sec are v)108 518.4 R +.019(alid v)-.25 F .019(alues. Of)-.25 F .019 +(fset must appear subsequent to track, sector and sec-)-.25 F +(tor length v)108 530.4 Q(alues.)-.25 E F2(Dir)87 547.2 Q +(ectory entries)-.18 E F0 .408 +(The directory is a sequence of directory entries \(also called e)108 +559.2 R .409(xtents\), which contain 32 bytes of the follo)-.15 F(w-) +-.25 E(ing structure:)108 571.2 Q 4.16(St F0)144 595.2 R 1.94 +(F1 F2 F3 F4 F5 F6 F7 E0)4.44 F 1.39(E1 E2 Xl)3.89 F 1.39(Bc Xh)5 F(Rc) +2.78 E 2.5(Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al)144 607.2 R +F2(St)108 631.2 Q F0(is the status; possible v)2.5 E(alues are:)-.25 E +(0\21115: used for \214le, status is the user number)144 655.2 Q .795(1\ +6\21131: used for \214le, status is the user number \(P2DOS\) or used f\ +or passw)144 667.2 R .794(ord e)-.1 F .794(xtent \(CP/M 3 or)-.15 F +(higher\))144 679.2 Q(32: disc label)144 691.2 Q +(33: time stamp \(P2DOS\))144 703.2 Q(0xE5: unused)144 715.2 Q +(CP/M tools)72 768 Q(February 18, 2012)151.35 E(1)192.2 E 0 Cg EP +%%Page: 2 2 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F/F1 10/Times-Bold@0 SF(F0\211E2)108 84 Q F0 .412 +(are the \214le name and its e)2.912 F 2.913(xtension. The)-.15 F 2.913 +(ym)-.15 G .413(ay consist of an)-2.913 F 2.913(yp)-.15 G .413 +(rintable 7 bit ASCII character b)-2.913 F(ut:)-.2 E F1(<)2.913 E 3.362 +(>.,;:=?*[])108 96 S F0 5.862(.T)-3.362 G .862 +(he \214le name must not be empty)-5.862 F 3.361(,t)-.65 G .861(he e) +-3.361 F .861(xtension may be empty)-.15 F 5.861(.B)-.65 G .861 +(oth are padded with)-5.861 F 2.831(blanks. The)108 108 R .331 +(highest bit of each character of the \214le name and e)2.831 F .331 +(xtension is used as attrib)-.15 F 2.832(ute. The)-.2 F(attrib)2.832 E +(utes)-.2 E(ha)108 120 Q .3 -.15(ve t)-.2 H(he follo).15 E +(wing meaning:)-.25 E(F0: requires set wheel byte \(Backgrounder II\)) +144 144 Q(F1: public \214le \(P2DOS, ZSDOS\), for)144 156 Q +(ground-only command \(Backgrounder II\))-.18 E +(F2: date stamp \(ZSDOS\), background-only commands \(Backgrounder II\)) +144 168 Q(F7: wheel protect \(ZSDOS\))144 180 Q(E0: read-only)144 192 Q +(E1: system \214le)144 204 Q(E2: archi)144 216 Q -.15(ve)-.25 G(d).15 E +.338(Public \214les \(visible under each user number\) are not supporte\ +d by CP/M 2.2, b)108 240 R .338(ut there is a patch and some)-.2 F +(free CP/M clones support them without an)108 252 Q 2.5(yp)-.15 G +(atches.)-2.5 E .827(The wheel byte is \(by def)108 268.8 R .828 +(ault\) the memory location at 0x4b)-.1 F 5.828(.I)-.4 G 3.328(fi)-5.828 +G 3.328(ti)-3.328 G 3.328(sz)-3.328 G .828(ero, only non-pri)-3.328 F +(vile)-.25 E .828(ged commands)-.15 F(may be e)108 280.8 Q -.15(xe)-.15 +G(cuted.).15 E F1(Xl)108 297.6 Q F0(and)2.546 E F1(Xh)2.546 E F0 .046 +(store the e)2.546 F .046(xtent number)-.15 F 5.046(.A)-.55 G .045 +(\214le may use more than one directory entry)-2.5 F 2.545(,i)-.65 G +2.545(fi)-2.545 G 2.545(tc)-2.545 G .045(ontains more blocks)-2.545 F +.21(than an e)108 309.6 R .21(xtent can hold.)-.15 F .21 +(In this case, more e)5.21 F .21 +(xtents are allocated and each of them is numbered sequentially)-.15 F +.457(with an e)108 321.6 R .457(xtent number)-.15 F 5.457(.I)-.55 G +2.957(fap)-5.457 G -.05(hy)-2.957 G .457(sical e).05 F .456 +(xtent stores more than 16k, it is considered to contain multiple logi-) +-.15 F .234(cal e)108 333.6 R .234 +(xtents, each pointing to 16k data, and the e)-.15 F .234 +(xtent number of the last used logical e)-.15 F .235(xtent is stored.) +-.15 F(Note:)5.235 E 1.55(Some formats decided to al)108 345.6 R -.1(wa) +-.1 G 1.549(ys store only one logical e).1 F 1.549(xtent in a ph)-.15 F +1.549(ysical e)-.05 F 1.549(xtent, thus w)-.15 F 1.549(asting e)-.1 F +(xtent)-.15 E 2.81(space. CP/M)108 357.6 R .31(2.2 allo)2.81 F .31 +(ws 512 e)-.25 F .31(xtents per \214le, CP/M 3 and higher allo)-.15 F +2.811(wu)-.25 G 2.811(pt)-2.811 G 2.811(o2)-2.811 G 2.811(048. Bit) +-2.811 F .311(5\2117 of Xl are 0, bit)2.811 F .577(0\2114 store the lo) +108 369.6 R .577(wer bits of the e)-.25 F .576(xtent number)-.15 F 5.576 +(.B)-.55 G .576 +(it 6 and 7 of Xh are 0, bit 0\2115 store the higher bits of the)-5.576 +F -.15(ex)108 381.6 S(tent number).15 E(.)-.55 E F1(Rc)108 398.4 Q F0 +(and)2.946 E F1(Bc)2.946 E F0 .446 +(determine the length of the data used by this e)2.946 F 2.946 +(xtent. The)-.15 F(ph)2.947 E .447(ysical e)-.05 F .447(xtent is di)-.15 +F .447(vided into logical)-.25 F -.15(ex)108 410.4 S .156 +(tents, each of them being 16k in size \(a ph).15 F .156(ysical e)-.05 F +.156(xtent must hold at least one logical e)-.15 F .156 +(xtent, e.g. a block-)-.15 F .053(size of 1024 byte with tw)108 422.4 R +.054(o-byte block pointers is not allo)-.1 F 2.554(wed\). Rc)-.25 F .054 +(stores the number of 128 byte records of)2.554 F .457 +(the last used logical e)108 434.4 R 2.957(xtent. Bc)-.15 F .456 +(stores the number of bytes in the last used record.)2.957 F .456(The v) +5.456 F .456(alue 0 means 128)-.25 F .654(for backw)108 446.4 R .654 +(ard compatibility with CP/M 2.2, which did not support Bc.)-.1 F .655 +(ISX records the number of unused)5.655 F(instead of used bytes in Bc.) +108 458.4 Q F1(Al)108 475.2 Q F0 .9(stores block pointers.)3.4 F .899(I\ +f the disk capacity is less than 256 blocks, Al is interpreted as 16 by\ +te-v)5.9 F(alues,)-.25 E .243(otherwise as 8 double-byte-v)108 487.2 R +2.743(alues. A)-.25 F .243 +(block pointer of 0 marks a hole in the \214le.)2.743 F .243 +(If a hole co)5.243 F -.15(ve)-.15 G .243(rs the range).15 F .341 +(of a full e)108 499.2 R .341(xtent, the e)-.15 F .341 +(xtent will not be allocated.)-.15 F .34(In particular)5.341 F 2.84(,t) +-.4 G .34(he \214rst e)-2.84 F .34 +(xtent of a \214le does not neccessarily)-.15 F(ha)108 511.2 Q .479 -.15 +(ve ex)-.2 H .179(tent number 0.).15 F 2.679<418c>5.179 G .18 +(le may not share blocks with other \214les, as its blocks w)-2.679 F +.18(ould be freed if the other)-.1 F .822 +(\214les is erased without a follo)108 523.2 R .822 +(wing disk system reset.)-.25 F .822 +(CP/M returns EOF when it reaches a hole, whereas)5.822 F +(UNIX returns zero-v)108 535.2 Q(alue bytes, which mak)-.25 E +(es holes in)-.1 E(visible.)-.4 E F1(Nati)87 552 Q .2 -.1(ve t)-.1 H +(ime stamps).1 E F0 1.053(P2DOS and CP/M Plus support time stamps, whic\ +h are stored in each fourth directory entry)108 564 R 6.054(.T)-.65 G +1.054(his entry)-6.054 F 1.3(contains the time stamps for the e)108 576 +R 1.299(xtents using the pre)-.15 F 1.299 +(vious three directory entries.)-.25 F 1.299(Note that you really)6.299 +F(ha)108 588 Q 1.294 -.15(ve t)-.2 H .994(ime stamps for each e).15 F +.994(xtent, no matter if it is the \214rst e)-.15 F .994 +(xtent of a \214le or not.)-.15 F .995(The structure of time)5.994 F +(stamp entries is:)108 600 Q 2.5(1b)144 624 S(yte status 0x21)-2.5 E 2.5 +(8b)144 636 S(ytes time stamp for third-last directory entry)-2.5 E 2.5 +(2b)144 648 S(ytes unused)-2.5 E 2.5(8b)144 660 S +(ytes time stamp for second-last directory entry)-2.5 E 2.5(2b)144 672 S +(ytes unused)-2.5 E 2.5(8b)144 684 S +(ytes time stamp for last directory entry)-2.5 E 2.872(At)108 708 S .372 +(ime stamp consists of tw)-2.872 F 2.872(od)-.1 G .372(ates: Creation a\ +nd modi\214cation date \(the latter being recorded when the \214le) +-2.872 F .935(is closed\).)108 720 R .936(CP/M Plus further allo)5.935 F +.936(ws optionally to record the access instead of creation date as \ +\214rst time)-.25 F(CP/M tools)72 768 Q(February 18, 2012)151.35 E(2) +192.2 E 0 Cg EP +%%Page: 3 3 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F(stamp.)108 84 Q 2.5(2b)144 108 S +(ytes \(little-endian\) days starting with 1 at 01-01-1978)-2.5 E 2.5 +(1b)144 120 S(yte hour in BCD format)-2.5 E 2.5(1b)144 132 S +(yte minute in BCD format)-2.5 E/F1 10/Times-Bold@0 SF +(DateStamper time stamps)87 160.8 Q F0 .552(The DateStamper softw)108 +172.8 R .552(are added functions to the BDOS to manage time stamps by a\ +llocating a read only)-.1 F .441(\214le with the name "!!!TIME&.D)108 +184.8 R -1.11(AT)-.4 G 2.941("i)1.11 G 2.941(nt)-2.941 G .441(he v) +-2.941 F .441(ery \214rst directory entry)-.15 F 2.941(,c)-.65 G -.15 +(ove)-2.941 G .441(ring the v).15 F .442(ery \214rst data blocks.)-.15 F +(It)5.442 E(contains one entry per directory entry with the follo)108 +196.8 Q(wing structure of 16 bytes:)-.25 E 2.5(5b)144 220.8 S +(ytes create date\214eld)-2.5 E 2.5(5b)144 232.8 S +(ytes access date\214eld)-2.5 E 2.5(5b)144 244.8 S +(ytes modify date\214eld)-2.5 E 2.5(1b)144 256.8 S(yte checksum)-2.5 E +.237(The checksum is only used on e)108 280.8 R -.15(ve)-.25 G .236(ry \ +8th entry \(last entry in 128-byte record\) and is the sum of the \214r\ +st 127).15 F(bytes of the record.)108 292.8 Q +(Each date\214eld has this structure:)5 E 2.5(1b)144 316.8 S +(yte BCD coded year \(no century)-2.5 E 2.5(,s)-.65 G 2.5(oi)-2.5 G 2.5 +(ti)-2.5 G 2.5(ss)-2.5 G(ane assuming an)-2.5 E 2.5(yy)-.15 G +(ear < 70 means 21st century\))-2.5 E 2.5(1b)144 328.8 S +(yte BCD coded month)-2.5 E 2.5(1b)144 340.8 S(yte BCD coded day)-2.5 E +2.608(1b)144 352.8 S .108(yte BCD coded hour or)-2.608 F 2.608(,i)-.4 G +2.608(ft)-2.608 G .108(he high bit is set, the high byte of a counter f\ +or systems without real)-2.608 F(time clock)144 364.8 Q 2.5(1b)144 376.8 +S(yte BCD coded minute, or the lo)-2.5 E 2.5(wb)-.25 G +(yte of the counter)-2.5 E F1(Disc labels)87 405.6 Q F0 .258(CP/M Plus \ +support disc labels, which are stored in an arbitrary directory entry) +108 417.6 R 5.257(.T)-.65 G .257(he structure of disc labels)-5.257 F +(is:)108 429.6 Q 2.5(1b)144 453.6 S(yte status 0x20)-2.5 E F1(F0\211E2) +144 465.6 Q F0(are the disc label)2.5 E 2.886(1b)144 477.6 S .386 +(yte mode: bit 7 acti)-2.886 F -.25(va)-.25 G .386(tes passw).25 F .387 +(ord protection, bit 6 causes time stamps on access, b)-.1 F .387 +(ut 5 causes)-.2 F .874(time stamps on modi\214cations, bit 4 causes ti\ +me stamps on creation and bit 0 is set when a label)144 489.6 R -.15(ex) +144 501.6 S 2.5(ists. Bit).15 F 2.5(4a)2.5 G(nd 6 are e)-2.5 E(xclusi) +-.15 E -.15(ve)-.25 G(ly set.).15 E 3.45(1b)144 513.6 S .95(yte passw) +-3.45 F .95(ord decode byte: T)-.1 F 3.45(od)-.8 G .951(ecode the passw) +-3.45 F .951(ord, xor this byte with the passw)-.1 F .951(ord bytes in) +-.1 F(re)144 525.6 Q -.15(ve)-.25 G(rse order).15 E 5(.T)-.55 G 2.5(oe) +-5.8 G(ncode a passw)-2.5 E +(ord, add its characters to get the decode byte.)-.1 E 2.5(2r)144 537.6 +S(eserv)-2.5 E(ed bytes)-.15 E 2.5(8p)144 549.6 S(assw)-2.5 E(ord bytes) +-.1 E 2.5(4b)144 561.6 S(ytes label creation time stamp)-2.5 E 2.5(4b) +144 573.6 S(ytes label modi\214cation time stamp)-2.5 E F1 -.1(Pa)87 +602.4 S(ssw).1 E(ords)-.1 E F0 1.484(CP/M Plus supports passw)108 614.4 +R 1.484(ords, which are stored in an arbitrary directory entry)-.1 F +6.484(.T)-.65 G 1.484(he structure of these)-6.484 F(entries is:)108 +626.4 Q 2.5(1b)144 650.4 S(yte status \(user number plus 16\))-2.5 E F1 +(F0\211E2)144 662.4 Q F0(are the \214le name and its e)2.5 E(xtension.) +-.15 E 3.171(1b)144 674.4 S .671(yte passw)-3.171 F .671 +(ord mode: bit 7 means passw)-.1 F .672 +(ord required for reading, bit 6 for writing and bit 5 for)-.1 F +(deleting.)144 686.4 Q 3.451(1b)144 698.4 S .951(yte passw)-3.451 F .951 +(ord decode byte: T)-.1 F 3.451(od)-.8 G .951(ecode the passw)-3.451 F +.95(ord, xor this byte with the passw)-.1 F .95(ord bytes in)-.1 F(re) +144 710.4 Q -.15(ve)-.25 G(rse order).15 E 5(.T)-.55 G 2.5(oe)-5.8 G +(ncode a passw)-2.5 E(ord, add its characters to get the decode byte.) +-.1 E 2.5(2r)144 722.4 S(eserv)-2.5 E(ed bytes)-.15 E(CP/M tools)72 768 +Q(February 18, 2012)151.35 E(3)192.2 E 0 Cg EP +%%Page: 4 4 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F 2.5(8p)144 84 S(assw)-2.5 E(ord bytes)-.1 E/F1 +10.95/Times-Bold@0 SF(SEE ALSO)72 112.8 Q/F2 10/Times-Italic@0 SF +(mkfs.cpm)108 124.8 Q F0(\(1\),).32 E F2(fsc)2.5 E(k.cpm)-.2 E F0 +(\(1\),).32 E F2(fsed.cpm)2.5 E F0(\(1\),).32 E F2(cpmls)2.5 E F0(\(1\)) +.27 E(CP/M tools)72 768 Q(February 18, 2012)151.35 E(4)192.2 E 0 Cg EP +%%Trailer +end +%%EOF diff --git a/Tools/unix/cpmtools/cpmchattr.1 b/Tools/unix/cpmtools/cpmchattr.1 new file mode 100644 index 00000000..f130d5c2 --- /dev/null +++ b/Tools/unix/cpmtools/cpmchattr.1 @@ -0,0 +1,92 @@ +.TH CPMCHATTR 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME \"{{{roff}}}\"{{{ +cpmchattr \- change file attributes on CP/M files +.\"}}} +.SH SYNOPSIS \"{{{ +.ad l +.B cpmchattr +.RB [ \-f +.IR format ] +.I image +.I attrib +.I file-pattern +\&... +.ad b +.\"}}} +.SH DESCRIPTION \"{{{ +\fBCpmchattr\fP changes the file attributes for files on CP/M disks. +.\"}}} +.SH OPTIONS \"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.IP "\fIattrib\fP" +Set the file attributes as given. +.\"}}} +.SH "FILE ATTRIBUTES" \"{{{ +The file attribute string can contain the characters +1,2,3,4,r,s,a,n and m. +The meanings of these are: +.TP +.B 1-4 +The CP/M "user attributes" F1-F4. CP/M does not assign any +meaning to these attributes, though MP/M does. +.TP +.B r +The file is read-only. This is the same as using +.I cpmchmod(1) +to revoke write permissions. +.TP +.B s +The file is a system file. This attribute can also be set by +.I cpmchmod(1). +.TP +.B a +The file has been backed up. +.TP +.B n +Reset all attributes to zero. So the string "n1r" resets all attributes and +then sets F1 and Read-Only. +.TP +.B m +Attributes after an m are unset rather than set. The string "12m34" sets +atttributes F1 and F2, and unsets F3 and F4. +.\"}}} +.SH "RETURN VALUE" \"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS \"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES \"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt + and copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR cpmchmod (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmchattr.c b/Tools/unix/cpmtools/cpmchattr.c new file mode 100644 index 00000000..be39130b --- /dev/null +++ b/Tools/unix/cpmtools/cpmchattr.c @@ -0,0 +1,119 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmchattr"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,i,usage=0,exitcode=0; + struct cpmSuperBlock drive; + struct cpmInode root; + int gargc; + char **gargv; + const char *attrs; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'T': devopts=optarg; break; + case 'f': format=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind>=(argc-2)) usage=1; + else + { + image=argv[optind++]; + attrs = argv[optind++]; + } + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-T dsktype] image [NMrsa1234] pattern ...\n",cmd); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev, image, O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + cpmglob(optind,argc,argv,&root,&gargc,&gargv); + for (i=0; i and copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR chmod (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmchmod.c b/Tools/unix/cpmtools/cpmchmod.c new file mode 100644 index 00000000..ad146965 --- /dev/null +++ b/Tools/unix/cpmtools/cpmchmod.c @@ -0,0 +1,89 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmchmod"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,i,usage=0,exitcode=0; + struct cpmSuperBlock drive; + struct cpmInode root; + int gargc; + char **gargv; + unsigned int mode; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'T': devopts=optarg; break; + case 'f': format=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind>=(argc-2)) usage=1; + else + { + image=argv[optind++]; + if (!sscanf(argv[optind++], "%o", &mode)) usage=1; + } + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] image mode pattern ...\n",cmd); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev, image, O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + cpmglob(optind,argc,argv,&root,&gargc,&gargv); + for (i=0; i. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmcp.c b/Tools/unix/cpmtools/cpmcp.c new file mode 100644 index 00000000..561c451f --- /dev/null +++ b/Tools/unix/cpmtools/cpmcp.c @@ -0,0 +1,299 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmcp"; +static int text=0; +static int preserve=0; + +/** + * Return the user number. + * @param s CP/M filename in 0[0]:aaaaaaaa.bbb format. + * @returns The user number or -1 for no match. + */ +static int userNumber(const char *s) /*{{{*/ +{ + if (isdigit(*s) && *(s+1)==':') return (*s-'0'); + if (isdigit(*s) && isdigit(*(s+1)) && *(s+2)==':') return (10*(*s-'0')+(*(s+1)-'0')); + return -1; +} +/*}}}*/ + +/** + * Copy one file from CP/M to UNIX. + * @param root The inode for the root directory. + * @param src The CP/M filename in 00aaaaaaaabbb format. + * @param dest The UNIX filename. + * @returns 0 for success, 1 for error. + */ +static int cpmToUnix(const struct cpmInode *root, const char *src, const char *dest) /*{{{*/ +{ + struct cpmInode ino; + int exitcode=0; + + if (cpmNamei(root,src,&ino)==-1) { fprintf(stderr,"%s: can not open `%s': %s\n",cmd,src,boo); exitcode=1; } + else + { + struct cpmFile file; + FILE *ufp; + + cpmOpen(&ino,&file,O_RDONLY); + if ((ufp=fopen(dest,text ? "w" : "wb"))==(FILE*)0) { fprintf(stderr,"%s: can not create %s: %s\n",cmd,dest,strerror(errno)); exitcode=1; } + else + { + int crpending=0; + int ohno=0; + int res; + char buf[4096]; + + while ((res=cpmRead(&file,buf,sizeof(buf)))>0) + { + int j; + + for (j=0; j=argc) usage(); + image=argv[optind++]; + + if (userNumber(argv[optind])>=0) /* cpm -> unix? */ /*{{{*/ + { + int i; + struct stat statbuf; + + for (i=optind; i<(argc-1); ++i) if (userNumber(argv[i])==-1) usage(); + todir=((argc-optind)>2); + if (stat(argv[argc-1],&statbuf)==-1) { if (todir) usage(); } + else if (S_ISDIR(statbuf.st_mode)) todir=1; else if (todir) usage(); + readcpm=1; + } + /*}}}*/ + else if (userNumber(argv[argc-1])>=0) /* unix -> cpm */ /*{{{*/ + { + int i; + + todir=0; + for (i=optind; i<(argc-1); ++i) if (userNumber(argv[i])>=0) usage(); + if ((argc-optind)>2 && *(strchr(argv[argc-1],':')+1)!='\0') usage(); + if (*(strchr(argv[argc-1],':')+1)=='\0') todir=1; + readcpm=0; + } + /*}}}*/ + else usage(); + /*}}}*/ + /* open image file */ /*{{{*/ + if ((err=Device_open(&super.dev,image,readcpm ? O_RDONLY : O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&super,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + if (readcpm) /* copy from CP/M to UNIX */ /*{{{*/ + { + int i; + char *last=argv[argc-1]; + + cpmglob(optind,argc-1,argv,&root,&gargc,&gargv); + /* trying to copy multiple files to a file? */ + if (gargc>1 && !todir) usage(); + for (i=0; i=' ' && !((c)&~0x7f) && (c)!='<' && (c)!='>' && (c)!='.' && (c)!=',' && (c)!=';' && (c)!=':' && (c)!='=' && (c)!='?' && (c)!='*' && (c)!= '[' && (c)!=']') +#define EXTENT(low,high) (((low)&0x1f)|(((high)&0x3f)<<5)) +#define EXTENTL(extent) ((extent)&0x1f) +#define EXTENTH(extent) (((extent>>5))&0x3f) + +#endif diff --git a/Tools/unix/cpmtools/cpmfs.c b/Tools/unix/cpmtools/cpmfs.c new file mode 100644 index 00000000..eaba3ad4 --- /dev/null +++ b/Tools/unix/cpmtools/cpmfs.c @@ -0,0 +1,1920 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpmdir.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* #defines */ /*{{{*/ +#undef CPMFS_DEBUG + +/* Number of _used_ bits per int */ + +#define INTBITS ((int)(sizeof(int)*8)) + +/* Convert BCD datestamp digits to binary */ + +#define BCD2BIN(x) ((((x)>>4)&0xf)*10 + ((x)&0xf)) + +#define BIN2BCD(x) (((((x)/10)&0xf)<<4) + (((x)%10)&0xf)) + +/* There are four reserved directory entries: ., .., [passwd] and [label]. +The first two of them refer to the same inode. */ + +#define RESERVED_ENTRIES 4 + +/* CP/M does not support any kind of inodes, so they are simulated. +Inode 0-(maxdir-1) correlate to the lowest extent number (not the first +extent of the file in the directory) of a file. Inode maxdir is the +root directory, inode maxdir+1 is the optional passwd file and inode +maxdir+2 the optional disk label. */ + +#define RESERVED_INODES 3 + +#define PASSWD_RECLEN 24 +/*}}}*/ + +extern char **environ; +const char *boo; +static mode_t s_ifdir=1; +static mode_t s_ifreg=1; + +/* memcpy7 -- Copy string, leaving 8th bit alone */ /*{{{*/ +static void memcpy7(char *dest, const char *src, int count) +{ + while (count--) + { + *dest = ((*dest) & 0x80) | ((*src) & 0x7F); + ++dest; + ++src; + } +} +/*}}}*/ + +/* file name conversions */ +/* splitFilename -- split file name into name and extension */ /*{{{*/ +static int splitFilename(const char *fullname, int type, char *name, char *ext, int *user) +{ + int i,j; + + assert(fullname!=(const char*)0); + assert(name!=(char*)0); + assert(ext!=(char*)0); + assert(user!=(int*)0); + memset(name,' ',8); + memset(ext,' ',3); + if (!isdigit(fullname[0]) || !isdigit(fullname[1])) + { + boo="illegal CP/M filename"; + return -1; + } + *user=10*(fullname[0]-'0')+(fullname[1]-'0'); + fullname+=2; + if ((fullname[0]=='\0') || *user>=((type&CPMFS_HI_USER) ? 32 : 16)) + { + boo="illegal CP/M filename"; + return -1; + } + for (i=0; i<8 && fullname[i] && fullname[i]!='.'; ++i) if (!ISFILECHAR(i,fullname[i])) + { + boo="illegal CP/M filename"; + return -1; + } + else name[i]=toupper(fullname[i]); + if (fullname[i]=='.') + { + ++i; + for (j=0; j<3 && fullname[i]; ++i,++j) if (!ISFILECHAR(1,fullname[i])) + { + boo="illegal CP/M filename"; + return -1; + } + else ext[j]=toupper(fullname[i]); + if (i==1 && j==0) + { + boo="illegal CP/M filename"; + return -1; + } + } + return 0; +} +/*}}}*/ +/* isMatching -- do two file names match? */ /*{{{*/ +static int isMatching(int user1, const char *name1, const char *ext1, int user2, const char *name2, const char *ext2) +{ + int i; + + assert(name1!=(const char*)0); + assert(ext1!=(const char*)0); + assert(name2!=(const char*)0); + assert(ext2!=(const char*)0); + if (user1!=user2) return 0; + for (i=0; i<8; ++i) if ((name1[i]&0x7f)!=(name2[i]&0x7f)) return 0; + for (i=0; i<3; ++i) if ((ext1[i]&0x7f)!=(ext2[i]&0x7f)) return 0; + return 1; +} +/*}}}*/ + +/* time conversions */ +/* cpm2unix_time -- convert CP/M time to UTC */ /*{{{*/ +static time_t cpm2unix_time(int days, int hour, int min) +{ + /* CP/M stores timestamps in local time. We don't know which */ + /* timezone was used and if DST was in effect. Assuming it was */ + /* the current offset from UTC is most sensible, but not perfect. */ + + int year,days_per_year; + static int days_per_month[]={31,0,31,30,31,30,31,31,30,31,30,31}; + char **old_environ; + static char gmt0[]="TZ=GMT0"; + static char *gmt_env[]={ gmt0, (char*)0 }; + struct tm tms; + time_t lt,t; + + time(<); + t=lt; + tms=*localtime(<); + old_environ=environ; + environ=gmt_env; + lt=mktime(&tms); + lt-=t; + tms.tm_sec=0; + tms.tm_min=((min>>4)&0xf)*10+(min&0xf); + tms.tm_hour=((hour>>4)&0xf)*10+(hour&0xf); + tms.tm_mday=1; + tms.tm_mon=0; + tms.tm_year=78; + tms.tm_isdst=-1; + for (;;) + { + year=tms.tm_year+1900; + days_per_year=((year%4)==0 && ((year%100) || (year%400)==0)) ? 366 : 365; + if (days>days_per_year) + { + days-=days_per_year; + ++tms.tm_year; + } + else break; + } + for (;;) + { + days_per_month[1]=(days_per_year==366) ? 29 : 28; + if (days>days_per_month[tms.tm_mon]) + { + days-=days_per_month[tms.tm_mon]; + ++tms.tm_mon; + } + else break; + } + t=mktime(&tms)+(days-1)*24*3600; + environ=old_environ; + t-=lt; + return t; +} +/*}}}*/ +/* unix2cpm_time -- convert UTC to CP/M time */ /*{{{*/ +static void unix2cpm_time(time_t now, int *days, int *hour, int *min) +{ + struct tm *tms; + int i; + + tms=localtime(&now); + *min=((tms->tm_min/10)<<4)|(tms->tm_min%10); + *hour=((tms->tm_hour/10)<<4)|(tms->tm_hour%10); + for (i=1978,*days=0; i<1900+tms->tm_year; ++i) + { + *days+=365; + if (i%4==0 && (i%100!=0 || i%400==0)) ++*days; + } + *days += tms->tm_yday+1; +} +/*}}}*/ +/* ds2unix_time -- convert DS to Unix time */ /*{{{*/ +static time_t ds2unix_time(const struct dsEntry *entry) +{ + struct tm tms; + int yr; + + if (entry->minute==0 && + entry->hour==0 && + entry->day==0 && + entry->month==0 && + entry->year==0) return 0; + + tms.tm_isdst = -1; + tms.tm_sec = 0; + tms.tm_min = BCD2BIN( entry->minute ); + tms.tm_hour = BCD2BIN( entry->hour ); + tms.tm_mday = BCD2BIN( entry->day ); + tms.tm_mon = BCD2BIN( entry->month ) - 1; + + yr = BCD2BIN(entry->year); + if (yr<70) yr+=100; + tms.tm_year = yr; + + return mktime(&tms); +} +/*}}}*/ +/* unix2ds_time -- convert Unix to DS time */ /*{{{*/ +static void unix2ds_time(time_t now, struct dsEntry *entry) +{ + struct tm *tms; + int yr; + + if ( now==0 ) + { + entry->minute=entry->hour=entry->day=entry->month=entry->year = 0; + } + else + { + tms=localtime(&now); + entry->minute = BIN2BCD( tms->tm_min ); + entry->hour = BIN2BCD( tms->tm_hour ); + entry->day = BIN2BCD( tms->tm_mday ); + entry->month = BIN2BCD( tms->tm_mon + 1 ); + + yr = tms->tm_year; + if ( yr>100 ) yr -= 100; + entry->year = BIN2BCD( yr ); + } +} +/*}}}*/ + +/* allocation vector bitmap functions */ +/* alvInit -- init allocation vector */ /*{{{*/ +static void alvInit(const struct cpmSuperBlock *d) +{ + int i,j,offset,block; + + assert(d!=(const struct cpmSuperBlock*)0); + /* clean bitmap */ /*{{{*/ + memset(d->alv,0,d->alvSize*sizeof(int)); + /*}}}*/ + /* mark directory blocks as used */ /*{{{*/ + *d->alv=(1<<((d->maxdir*32+d->blksiz-1)/d->blksiz))-1; + /*}}}*/ + for (i=0; imaxdir; ++i) /* mark file blocks as used */ /*{{{*/ + { + if (d->dir[i].status>=0 && d->dir[i].status<=(d->type&CPMFS_HI_USER ? 31 : 15)) + { +#ifdef CPMFS_DEBUG + fprintf(stderr,"alvInit: allocate extent %d\n",i); +#endif + for (j=0; j<16; ++j) + { + block=(unsigned char)d->dir[i].pointers[j]; + if (d->size>=256) block+=(((unsigned char)d->dir[i].pointers[++j])<<8); + if (block && blocksize) + { +#ifdef CPMFS_DEBUG + fprintf(stderr,"alvInit: allocate block %d\n",block); +#endif + offset=block/INTBITS; + d->alv[offset]|=(1<alvSize; ++i) + { + for (j=0,bits=drive->alv[i]; j=drive->size) + { + boo="device full"; + return -1; + } + drive->alv[i] |= (1<>= 1; + } + } + boo="device full"; + return -1; +} +/*}}}*/ + +/* logical block I/O */ +/* readBlock -- read a (partial) block */ /*{{{*/ +static int readBlock(const struct cpmSuperBlock *d, int blockno, char *buffer, int start, int end) +{ + int sect, track, counter; + + assert(d); + assert(blockno>=0); + assert(buffer); + if (blockno>=d->size) + { + boo="Attempting to access block beyond end of disk"; + return -1; + } + if (end<0) end=d->blksiz/d->secLength-1; + sect=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)%d->sectrk; + track=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)/d->sectrk; + for (counter=0; counter<=end; ++counter) + { + const char *err; + + assert(d->skewtab[sect]>=0); + assert(d->skewtab[sect]sectrk); + if (counter>=start && (err=Device_readSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter)))) + { + boo=err; + return -1; + } + ++sect; + if (sect>=d->sectrk) + { + sect = 0; + ++track; + } + } + return 0; +} +/*}}}*/ +/* writeBlock -- write a (partial) block */ /*{{{*/ +static int writeBlock(const struct cpmSuperBlock *d, int blockno, const char *buffer, int start, int end) +{ + int sect, track, counter; + + assert(blockno>=0); + assert(blocknosize); + assert(buffer!=(const char*)0); + if (end < 0) end=d->blksiz/d->secLength-1; + sect = (blockno*(d->blksiz/d->secLength))%d->sectrk; + track = (blockno*(d->blksiz/d->secLength))/d->sectrk+d->boottrk; + for (counter = 0; counter<=end; ++counter) + { + const char *err; + + if (counter>=start && (err=Device_writeSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter)))) + { + boo=err; + return -1; + } + ++sect; + if (sect>=d->sectrk) + { + sect=0; + ++track; + } + } + return 0; +} +/*}}}*/ + +/* directory management */ +/* findFileExtent -- find first/next extent for a file */ /*{{{*/ +static int findFileExtent(const struct cpmSuperBlock *sb, int user, const char *name, const char *ext, int start, int extno) +{ + boo="file already exists"; + for (; startmaxdir; ++start) + { + if + ( + ((unsigned char)sb->dir[start].status)<=(sb->type&CPMFS_HI_USER ? 31 : 15) + && (extno==-1 || (EXTENT(sb->dir[start].extnol,sb->dir[start].extnoh)/sb->extents)==(extno/sb->extents)) + && isMatching(user,name,ext,sb->dir[start].status,sb->dir[start].name,sb->dir[start].ext) + ) return start; + } + boo="file not found"; + return -1; +} +/*}}}*/ +/* findFreeExtent -- find first free extent */ /*{{{*/ +static int findFreeExtent(const struct cpmSuperBlock *drive) +{ + int i; + + for (i=0; imaxdir; ++i) if (drive->dir[i].status==(char)0xe5) return (i); + boo="directory full"; + return -1; +} +/*}}}*/ +/* updateTimeStamps -- convert time stamps to CP/M format */ /*{{{*/ +static void updateTimeStamps(const struct cpmInode *ino, int extent) +{ + struct PhysDirectoryEntry *date; + int i; + int ca_min,ca_hour,ca_days,u_min,u_hour,u_days; + + if (!S_ISREG(ino->mode)) return; +#ifdef CPMFS_DEBUG + fprintf(stderr,"CPMFS: updating time stamps for inode %d (%d)\n",extent,extent&3); +#endif + unix2cpm_time(ino->sb->cnotatime ? ino->ctime : ino->atime,&ca_days,&ca_hour,&ca_min); + unix2cpm_time(ino->mtime,&u_days,&u_hour,&u_min); + if ((ino->sb->type&CPMFS_CPM3_DATES) && (date=ino->sb->dir+(extent|3))->status==0x21) + { + ino->sb->dirtyDirectory=1; + switch (extent&3) + { + case 0: /* first entry */ /*{{{*/ + { + date->name[0]=ca_days&0xff; date->name[1]=ca_days>>8; + date->name[2]=ca_hour; + date->name[3]=ca_min; + date->name[4]=u_days&0xff; date->name[5]=u_days>>8; + date->name[6]=u_hour; + date->name[7]=u_min; + break; + } + /*}}}*/ + case 1: /* second entry */ /*{{{*/ + { + date->ext[2]=ca_days&0xff; date->extnol=ca_days>>8; + date->lrc=ca_hour; + date->extnoh=ca_min; + date->blkcnt=u_days&0xff; date->pointers[0]=u_days>>8; + date->pointers[1]=u_hour; + date->pointers[2]=u_min; + break; + } + /*}}}*/ + case 2: /* third entry */ /*{{{*/ + { + date->pointers[5]=ca_days&0xff; date->pointers[6]=ca_days>>8; + date->pointers[7]=ca_hour; + date->pointers[8]=ca_min; + date->pointers[9]=u_days&0xff; date->pointers[10]=u_days>>8; + date->pointers[11]=u_hour; + date->pointers[12]=u_min; + break; + } + /*}}}*/ + } + } +} +/*}}}*/ +/* updateDsStamps -- set time in datestamper file */ /*{{{*/ +static void updateDsStamps(const struct cpmInode *ino, int extent) +{ + int yr; + struct tm *cpm_time; + struct dsDate *stamp; + + if (!S_ISREG(ino->mode)) return; + if ( !(ino->sb->type&CPMFS_DS_DATES) ) return; + +#ifdef CPMFS_DEBUG + fprintf(stderr,"CPMFS: updating ds stamps for inode %d (%d)\n",extent,extent&3); +#endif + + /* Get datestamp struct */ + stamp = ino->sb->ds+extent; + + unix2ds_time( ino->mtime, &stamp->modify ); + unix2ds_time( ino->ctime, &stamp->create ); + unix2ds_time( ino->atime, &stamp->access ); + + ino->sb->dirtyDs = 1; +} +/*}}}*/ +/* readTimeStamps -- read CP/M time stamp */ /*{{{*/ +static int readTimeStamps(struct cpmInode *i, int lowestExt) +{ + /* variables */ /*{{{*/ + struct PhysDirectoryEntry *date; + int u_days=0,u_hour=0,u_min=0; + int ca_days=0,ca_hour=0,ca_min=0; + int protectMode=0; + /*}}}*/ + + if ( (i->sb->type&CPMFS_CPM3_DATES) && (date=i->sb->dir+(lowestExt|3))->status==0x21 ) + { + switch (lowestExt&3) + { + case 0: /* first entry of the four */ /*{{{*/ + { + ca_days=((unsigned char)date->name[0])+(((unsigned char)date->name[1])<<8); + ca_hour=(unsigned char)date->name[2]; + ca_min=(unsigned char)date->name[3]; + u_days=((unsigned char)date->name[4])+(((unsigned char)date->name[5])<<8); + u_hour=(unsigned char)date->name[6]; + u_min=(unsigned char)date->name[7]; + protectMode=(unsigned char)date->ext[0]; + break; + } + /*}}}*/ + case 1: /* second entry */ /*{{{*/ + { + ca_days=((unsigned char)date->ext[2])+(((unsigned char)date->extnol)<<8); + ca_hour=(unsigned char)date->lrc; + ca_min=(unsigned char)date->extnoh; + u_days=((unsigned char)date->blkcnt)+(((unsigned char)date->pointers[0])<<8); + u_hour=(unsigned char)date->pointers[1]; + u_min=(unsigned char)date->pointers[2]; + protectMode=(unsigned char)date->pointers[3]; + break; + } + /*}}}*/ + case 2: /* third one */ /*{{{*/ + { + ca_days=((unsigned char)date->pointers[5])+(((unsigned char)date->pointers[6])<<8); + ca_hour=(unsigned char)date->pointers[7]; + ca_min=(unsigned char)date->pointers[8]; + u_days=((unsigned char)date->pointers[9])+(((unsigned char)date->pointers[10])<<8); + u_hour=(unsigned char)date->pointers[11]; + u_min=(unsigned char)date->pointers[12]; + protectMode=(unsigned char)date->pointers[13]; + break; + } + /*}}}*/ + } + if (i->sb->cnotatime) + { + i->ctime=cpm2unix_time(ca_days,ca_hour,ca_min); + i->atime=0; + } + else + { + i->ctime=0; + i->atime=cpm2unix_time(ca_days,ca_hour,ca_min); + } + i->mtime=cpm2unix_time(u_days,u_hour,u_min); + } + else + { + i->atime=i->mtime=i->ctime=0; + protectMode=0; + } + + return protectMode; +} +/*}}}*/ +/* readDsStamps -- read datestamper time stamp */ /*{{{*/ +static void readDsStamps(struct cpmInode *i, int lowestExt) +{ + struct dsDate *stamp; + + if ( !(i->sb->type&CPMFS_DS_DATES) ) return; + + /* Get datestamp */ + stamp = i->sb->ds+lowestExt; + + i->mtime = ds2unix_time(&stamp->modify); + i->ctime = ds2unix_time(&stamp->create); + i->atime = ds2unix_time(&stamp->access); +} +/*}}}*/ + +/* match -- match filename against a pattern */ /*{{{*/ +static int recmatch(const char *a, const char *pattern) +{ + int first=1; + + assert(a); + assert(pattern); + while (*pattern) + { + switch (*pattern) + { + case '*': + { + if (*a=='.' && first) return 1; + ++pattern; + while (*a) if (recmatch(a,pattern)) return 1; else ++a; + break; + } + case '?': + { + if (*a) { ++a; ++pattern; } else return 0; + break; + } + default: if (tolower(*a)==tolower(*pattern)) { ++a; ++pattern; } else return 0; + } + first=0; + } + return (*pattern=='\0' && *a=='\0'); +} + +int match(const char *a, const char *pattern) +{ + int user; + char pat[255]; + + assert(a); + assert(pattern); + assert(strlen(pattern)<255); + if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; } + else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; } + else user=-1; + if (user==-1) sprintf(pat,"??%s",pattern); + else sprintf(pat,"%02d%s",user,pattern); + return recmatch(a,pat); +} + +/*}}}*/ +/* cpmglob -- expand CP/M style wildcards */ /*{{{*/ +void cpmglob(int optin, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv) +{ + struct cpmFile dir; + int entries,dirsize=0; + struct cpmDirent *dirent=(struct cpmDirent*)0; + int gargcap=0,i,j; + + *gargv=(char**)0; + *gargc=0; + cpmOpendir(root,&dir); + entries=0; + dirsize=8; + dirent=malloc(sizeof(struct cpmDirent)*dirsize); + while (cpmReaddir(&dir,&dirent[entries])) + { + ++entries; + if (entries==dirsize) dirent=realloc(dirent,sizeof(struct cpmDirent)*(dirsize*=2)); + } + for (i=optin; ilibdskGeometry[0] = '\0'; + d->type=0; + if ( + (fp=fopen("diskdefs","r"))==(FILE*)0 && + (ddenv && ((fp=fopen(DISKDEFS,"r"))==(FILE*)0)) && + (fp=fopen(DISKDEFS,"r"))==(FILE*)0) + { + fprintf(stderr,"%s: Neither `diskdefs' nor `" DISKDEFS "' could be opened.\n",cmd); + exit(1); + } + while (fgets(line,sizeof(line),fp)!=(char*)0) + { + int argc; + char *argv[2]; + char *s; + + /* Allow inline comments preceded by ; or # */ + s = strchr(line, '#'); + if (s) strcpy(s, "\n"); + s = strchr(line, ';'); + if (s) strcpy(s, "\n"); + + for (argc=0; argc<1 && (argv[argc]=strtok(argc ? (char*)0 : line," \t\n")); ++argc); + if ((argv[argc]=strtok((char*)0,"\n"))!=(char*)0) ++argc; + if (insideDef) + { + if (argc==1 && strcmp(argv[0],"end")==0) + { + insideDef=0; + d->size=(d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz; + if (d->extents==0) d->extents=((d->size>=256 ? 8 : 16)*d->blksiz)/16384; + if (d->extents==0) d->extents=1; + if (found) break; + } + else if (argc==2) + { + if (strcmp(argv[0],"seclen")==0) d->secLength=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"tracks")==0) d->tracks=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"sectrk")==0) d->sectrk=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"blocksize")==0) d->blksiz=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"maxdir")==0) d->maxdir=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"skew")==0) d->skew=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"skewtab")==0) + { + int pass,sectors; + + for (pass=0; pass<2; ++pass) + { + sectors=0; + for (s=argv[1]; *s; ) + { + int phys; + char *end; + + phys=strtol(s,&end,10); + if (pass==1) d->skewtab[sectors]=phys; + if (end==s) + { + fprintf(stderr,"%s: invalid skewtab `%s' at `%s'\n",cmd,argv[1],s); + exit(1); + } + s=end; + ++sectors; + if (*s==',') ++s; + } + if (pass==0) d->skewtab=malloc(sizeof(int)*sectors); + } + } + else if (strcmp(argv[0],"boottrk")==0) d->boottrk=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"offset")==0) + { + off_t val; + unsigned int multiplier; + char *endptr; + + errno=0; + multiplier=1; + val = strtol(argv[1],&endptr,10); + if ((errno==ERANGE && val==LONG_MAX)||(errno!=0 && val<=0)) + { + fprintf(stderr,"%s: invalid offset value \"%s\" - %s\n",cmd,argv[1],strerror(errno)); + exit(1); + } + if (endptr==argv[1]) + { + fprintf(stderr,"%s: offset value \"%s\" is not a number\n",cmd,argv[1]); + exit(1); + } + if (*endptr!='\0') + { + /* Have a unit specifier */ + switch (toupper(*endptr)) + { + case 'K': + multiplier=1024; + break; + case 'M': + multiplier=1024*1024; + break; + case 'T': + if (d->sectrk<0||d->tracks<0||d->secLength<0) + { + fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength\n",cmd); + exit(1); + } + multiplier=d->sectrk*d->secLength; + break; + case 'S': + if (d->sectrk<0||d->tracks<0||d->secLength<0) + { + fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength\n",cmd); + exit(1); + } + multiplier=d->secLength; + break; + default: + fprintf(stderr,"%s: unknown unit specifier \"%c\"\n",cmd,*endptr); + exit(1); + } + } + if (val*multiplier>INT_MAX) + { + fprintf(stderr,"%s: effective offset is out of range\n",cmd); + exit(1); + } + d->offset=val*multiplier; + } + else if (strcmp(argv[0],"logicalextents")==0) d->extents=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"os")==0) + { + if (strcmp(argv[1],"2.2" )==0) d->type|=CPMFS_DR22; + else if (strcmp(argv[1],"3" )==0) d->type|=CPMFS_DR3; + else if (strcmp(argv[1],"isx" )==0) d->type|=CPMFS_ISX; + else if (strcmp(argv[1],"p2dos")==0) d->type|=CPMFS_P2DOS; + else if (strcmp(argv[1],"zsys" )==0) d->type|=CPMFS_ZSYS; + else + { + fprintf(stderr, "%s: invalid OS type `%s'\n", cmd, argv[1]); + exit(1); + } + } + else if (strcmp(argv[0], "libdsk:format")==0) + { + strncpy(d->libdskGeometry, argv[1], sizeof(d->libdskGeometry) - 1); + d->libdskGeometry[sizeof(d->libdskGeometry) - 1] = 0; + } + } + else if (argc>0 && argv[0][0]!='#' && argv[0][0]!=';') + { + fprintf(stderr,"%s: invalid keyword `%s'\n",cmd,argv[0]); + exit(1); + } + } + else if (argc==2 && strcmp(argv[0],"diskdef")==0) + { + insideDef=1; + d->skew=1; + d->extents=0; + d->type=CPMFS_DR22; + d->skewtab=(int*)0; + d->offset=0; + d->boottrk=d->secLength=d->sectrk=d->tracks=-1; + d->libdskGeometry[0] = 0; + if (strcmp(argv[1],format)==0) found=1; + } + } + fclose(fp); + if (!found) + { + fprintf(stderr,"%s: unknown format %s\n",cmd,format); + exit(1); + } + if (d->boottrk<0) + { + fprintf(stderr, "%s: boottrk parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + if (d->secLength<0) + { + fprintf(stderr, "%s: secLength parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + if (d->sectrk<0) + { + fprintf(stderr, "%s: sectrk parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + if (d->tracks<0) + { + fprintf(stderr, "%s: tracks parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + return 0; +} +/*}}}*/ +/* amsReadSuper -- read super block from amstrad disk */ /*{{{*/ +static int amsReadSuper(struct cpmSuperBlock *d, const char *format) +{ + unsigned char boot_sector[512], *boot_spec; + const char *err; + + Device_setGeometry(&d->dev,512,9,40,0,"pcw180"); + if ((err=Device_readSector(&d->dev, 0, 0, (char *)boot_sector))) + { + fprintf(stderr,"%s: Failed to read Amstrad superblock (%s)\n",cmd,err); + exit(1); + } + boot_spec=(boot_sector[0] == 0 || boot_sector[0] == 3)?boot_sector:(unsigned char*)0; + /* Check for JCE's extension to allow Amstrad and MSDOS superblocks + * in the same sector (for the PCW16) + */ + if + ( + (boot_sector[0] == 0xE9 || boot_sector[0] == 0xEB) + && !memcmp(boot_sector + 0x2B, "CP/M", 4) + && !memcmp(boot_sector + 0x33, "DSK", 3) + && !memcmp(boot_sector + 0x7C, "CP/M", 4) + ) boot_spec = boot_sector + 128; + if (boot_spec==(unsigned char*)0) + { + fprintf(stderr,"%s: Amstrad superblock not present\n",cmd); + exit(1); + } + /* boot_spec[0] = format number: 0 for SS SD, 3 for DS DD + [1] = single/double sided and density flags + [2] = cylinders per side + [3] = sectors per cylinder + [4] = Physical sector shift, 2 => 512 + [5] = Reserved track count + [6] = Block shift + [7] = No. of directory blocks + */ + d->type = 0; + d->type |= CPMFS_DR3; /* Amstrads are CP/M 3 systems */ + d->secLength = 128 << boot_spec[4]; + d->tracks = boot_spec[2]; + if (boot_spec[1] & 3) d->tracks *= 2; + d->sectrk = boot_spec[3]; + d->blksiz = 128 << boot_spec[6]; + d->maxdir = (d->blksiz / 32) * boot_spec[7]; + d->skew = 1; /* Amstrads skew at the controller level */ + d->skewtab = (int*)0; + d->boottrk = boot_spec[5]; + d->offset = 0; + d->size = (d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz; + d->extents = ((d->size>=256 ? 8 : 16)*d->blksiz)/16384; + d->libdskGeometry[0] = 0; /* LibDsk can recognise an Amstrad superblock + * and autodect */ + + return 0; +} +/*}}}*/ +/* cpmCheckDs -- read all datestamper timestamps */ /*{{{*/ +int cpmCheckDs(struct cpmSuperBlock *sb) +{ + int dsoffset, dsblks, dsrecs, off, i; + unsigned char *buf; + + if (!isMatching(0,"!!!TIME&","DAT",sb->dir->status,sb->dir->name,sb->dir->ext)) return -1; + + /* Offset to ds file in alloc blocks */ + dsoffset=(sb->maxdir*32+(sb->blksiz-1))/sb->blksiz; + + dsrecs=(sb->maxdir+7)/8; + dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz; + + /* Allocate buffer */ + sb->ds=malloc(dsblks*sb->blksiz); + + /* Read ds file in its entirety */ + off=0; + for (i=dsoffset; ids)+off,0,-1)==-1) return -1; + off+=sb->blksiz; + } + + /* Verify checksums */ + buf = (unsigned char *)sb->ds; + for (i=0; ids); + sb->ds = (struct dsDate *)0; + return -1; + } + buf += 128; + } + return 0; +} +/*}}}*/ +/* cpmReadSuper -- get DPB and init in-core data for drive */ /*{{{*/ +int cpmReadSuper(struct cpmSuperBlock *d, struct cpmInode *root, const char *format) +{ + while (s_ifdir && !S_ISDIR(s_ifdir)) s_ifdir<<=1; + assert(s_ifdir); + while (s_ifreg && !S_ISREG(s_ifreg)) s_ifreg<<=1; + assert(s_ifreg); + if (strcmp(format,"amstrad")==0) amsReadSuper(d,format); + else diskdefReadSuper(d,format); + boo = Device_setGeometry(&d->dev,d->secLength,d->sectrk,d->tracks,d->offset,d->libdskGeometry); + if (boo) return -1; + + if (d->skewtab==(int*)0) /* generate skew table */ /*{{{*/ + { + int i,j,k; + + if (( d->skewtab = malloc(d->sectrk*sizeof(int))) == (int*)0) + { + boo=strerror(errno); + return -1; + } + memset(d->skewtab,0,d->sectrk*sizeof(int)); + for (i=j=0; isectrk; ++i,j=(j+d->skew)%d->sectrk) + { + while (1) + { + assert(isectrk); + assert(jsectrk); + for (k=0; kskewtab[k]!=j; ++k); + if (ksectrk; + else break; + } + d->skewtab[i]=j; + } + } + /*}}}*/ + /* initialise allocation vector bitmap */ /*{{{*/ + { + d->alvSize=((d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz+INTBITS-1)/INTBITS; + if ((d->alv=malloc(d->alvSize*sizeof(int)))==(int*)0) + { + boo=strerror(errno); + return -1; + } + } + /*}}}*/ + /* allocate directory buffer */ /*{{{*/ + assert(sizeof(struct PhysDirectoryEntry)==32); + if ((d->dir=malloc(((d->maxdir*32+d->blksiz-1)/d->blksiz)*d->blksiz))==(struct PhysDirectoryEntry*)0) + { + boo=strerror(errno); + return -1; + } + /*}}}*/ + if (d->dev.opened==0) /* create empty directory in core */ /*{{{*/ + { + memset(d->dir,0xe5,d->maxdir*32); + } + /*}}}*/ + else /* read directory in core */ /*{{{*/ + { + int i,blocks,entry; + + blocks=(d->maxdir*32+d->blksiz-1)/d->blksiz; + entry=0; + for (i=0; idir+entry),0,-1)==-1) return -1; + entry+=(d->blksiz/32); + } + } + /*}}}*/ + alvInit(d); + if (d->type&CPMFS_CPM3_OTHER) /* read additional superblock information */ /*{{{*/ + { + int i; + + /* passwords */ /*{{{*/ + { + int passwords=0; + + for (i=0; imaxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) ++passwords; +#ifdef CPMFS_DEBUG + fprintf(stderr,"getformat: found %d passwords\n",passwords); +#endif + if ((d->passwdLength=passwords*PASSWD_RECLEN)) + { + if ((d->passwd=malloc(d->passwdLength))==(char*)0) + { + boo="out of memory"; + return -1; + } + for (i=0,passwords=0; imaxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) + { + int j,pb; + char *p=d->passwd+(passwords++*PASSWD_RECLEN); + + p[0]='0'+(d->dir[i].status-16)/10; + p[1]='0'+(d->dir[i].status-16)%10; + for (j=0; j<8; ++j) p[2+j]=d->dir[i].name[j]&0x7f; + p[10]=(d->dir[i].ext[0]&0x7f)==' ' ? ' ' : '.'; + for (j=0; j<3; ++j) p[11+j]=d->dir[i].ext[j]&0x7f; + p[14]=' '; + pb=(unsigned char)d->dir[i].lrc; + for (j=0; j<8; ++j) p[15+j]=((unsigned char)d->dir[i].pointers[7-j])^pb; +#ifdef CPMFS_DEBUG + p[23]='\0'; + fprintf(stderr,"getformat: %s\n",p); +#endif + p[23]='\n'; + } + } + } + /*}}}*/ + /* disc label */ /*{{{*/ + for (i=0; imaxdir; ++i) if (d->dir[i].status==(char)0x20) + { + int j; + + d->cnotatime=d->dir[i].extnol&0x10; + if (d->dir[i].extnol&0x1) + { + d->labelLength=12; + if ((d->label=malloc(d->labelLength))==(char*)0) + { + boo="out of memory"; + return -1; + } + for (j=0; j<8; ++j) d->label[j]=d->dir[i].name[j]&0x7f; + for (j=0; j<3; ++j) d->label[8+j]=d->dir[i].ext[j]&0x7f; + d->label[11]='\n'; + } + else + { + d->labelLength=0; + } + break; + } + if (i==d->maxdir) + { + d->cnotatime=1; + d->labelLength=0; + } + /*}}}*/ + } + /*}}}*/ + else + { + d->passwdLength=0; + d->cnotatime=1; + d->labelLength=0; + } + d->root=root; + d->dirtyDirectory = 0; + root->ino=d->maxdir; + root->sb=d; + root->mode=(s_ifdir|0777); + root->size=0; + root->atime=root->mtime=root->ctime=0; + + d->dirtyDs=0; + if (cpmCheckDs(d)==0) d->type|=CPMFS_DS_DATES; + else d->ds=(struct dsDate*)0; + + return 0; +} +/*}}}*/ +/* syncDs -- write all datestamper timestamps */ /*{{{*/ +static int syncDs(const struct cpmSuperBlock *sb) +{ + if (sb->dirtyDs) + { + int dsoffset, dsblks, dsrecs, off, i; + unsigned char *buf; + + dsrecs=(sb->maxdir+7)/8; + + /* Re-calculate checksums */ + buf = (unsigned char *)sb->ds; + for ( i=0; imaxdir*32+(sb->blksiz-1))/sb->blksiz; + dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz; + + off=0; + for (i=dsoffset; ids))+off,0,-1)==-1) return -1; + off+=sb->blksiz; + } + } + return 0; +} +/*}}}*/ +/* cpmSync -- write directory back */ /*{{{*/ +int cpmSync(struct cpmSuperBlock *sb) +{ + if (sb->dirtyDirectory) + { + int i,blocks,entry; + + blocks=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz; + entry=0; + for (i=0; idir+entry),0,-1)==-1) return -1; + entry+=(sb->blksiz/32); + } + sb->dirtyDirectory=0; + } + if (sb->type&CPMFS_DS_DATES) syncDs(sb); + return 0; +} +/*}}}*/ +/* cpmUmount -- free super block */ /*{{{*/ +void cpmUmount(struct cpmSuperBlock *sb) +{ + cpmSync(sb); + if (sb->type&CPMFS_DS_DATES) free(sb->ds); + free(sb->alv); + free(sb->skewtab); + free(sb->dir); + if (sb->passwdLength) free(sb->passwd); +} +/*}}}*/ + +/* cpmNamei -- map name to inode */ /*{{{*/ +int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i) +{ + /* variables */ /*{{{*/ + int user; + char name[8],extension[3]; + int highestExtno,highestExt=-1,lowestExtno,lowestExt=-1; + int protectMode=0; + /*}}}*/ + + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + if (strcmp(filename,".")==0 || strcmp(filename,"..")==0) /* root directory */ /*{{{*/ + { + *i=*dir; + return 0; + } + /*}}}*/ + else if (strcmp(filename,"[passwd]")==0 && dir->sb->passwdLength) /* access passwords */ /*{{{*/ + { + i->attr=0; + i->ino=dir->sb->maxdir+1; + i->mode=s_ifreg|0444; + i->sb=dir->sb; + i->atime=i->mtime=i->ctime=0; + i->size=i->sb->passwdLength; + return 0; + } + /*}}}*/ + else if (strcmp(filename,"[label]")==0 && dir->sb->labelLength) /* access label */ /*{{{*/ + { + i->attr=0; + i->ino=dir->sb->maxdir+2; + i->mode=s_ifreg|0444; + i->sb=dir->sb; + i->atime=i->mtime=i->ctime=0; + i->size=i->sb->labelLength; + return 0; + } + /*}}}*/ + if (splitFilename(filename,dir->sb->type,name,extension,&user)==-1) return -1; + /* find highest and lowest extent */ /*{{{*/ + { + int extent; + + i->size=0; + extent=-1; + highestExtno=-1; + lowestExtno=2049; + while ((extent=findFileExtent(dir->sb,user,name,extension,extent+1,-1))!=-1) + { + int extno=EXTENT(dir->sb->dir[extent].extnol,dir->sb->dir[extent].extnoh); + + if (extno>highestExtno) + { + highestExtno=extno; + highestExt=extent; + } + if (extnosize=highestExtno*16384; + if (dir->sb->size<256) for (block=15; block>=0; --block) + { + if (dir->sb->dir[highestExt].pointers[block]) break; + } + else for (block=7; block>=0; --block) + { + if (dir->sb->dir[highestExt].pointers[2*block] || dir->sb->dir[highestExt].pointers[2*block+1]) break; + } + if (dir->sb->dir[highestExt].blkcnt) i->size+=((dir->sb->dir[highestExt].blkcnt&0xff)-1)*128; + if (dir->sb->type & CPMFS_ISX) + { + i->size += (128 - dir->sb->dir[highestExt].lrc); + } + else + { + i->size+=dir->sb->dir[highestExt].lrc ? (dir->sb->dir[highestExt].lrc&0xff) : 128; + } +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmNamei: size=%ld\n",(long)i->size); +#endif + } + /*}}}*/ + i->ino=lowestExt; + i->mode=s_ifreg; + i->sb=dir->sb; + + /* read timestamps */ /*{{{*/ + protectMode = readTimeStamps(i,lowestExt); + /*}}}*/ + + /* Determine the inode attributes */ + i->attr = 0; + if (dir->sb->dir[lowestExt].name[0]&0x80) i->attr |= CPM_ATTR_F1; + if (dir->sb->dir[lowestExt].name[1]&0x80) i->attr |= CPM_ATTR_F2; + if (dir->sb->dir[lowestExt].name[2]&0x80) i->attr |= CPM_ATTR_F3; + if (dir->sb->dir[lowestExt].name[3]&0x80) i->attr |= CPM_ATTR_F4; + if (dir->sb->dir[lowestExt].ext [0]&0x80) i->attr |= CPM_ATTR_RO; + if (dir->sb->dir[lowestExt].ext [1]&0x80) i->attr |= CPM_ATTR_SYS; + if (dir->sb->dir[lowestExt].ext [2]&0x80) i->attr |= CPM_ATTR_ARCV; + if (protectMode&0x20) i->attr |= CPM_ATTR_PWDEL; + if (protectMode&0x40) i->attr |= CPM_ATTR_PWWRITE; + if (protectMode&0x80) i->attr |= CPM_ATTR_PWREAD; + + if (dir->sb->dir[lowestExt].ext[1]&0x80) i->mode|=01000; + i->mode|=0444; + if (!(dir->sb->dir[lowestExt].ext[0]&0x80)) i->mode|=0222; + if (extension[0]=='C' && extension[1]=='O' && extension[2]=='M') i->mode|=0111; + + readDsStamps(i,lowestExt); + + return 0; +} +/*}}}*/ +/* cpmStatFS -- statfs */ /*{{{*/ +void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf) +{ + int i; + struct cpmSuperBlock *d; + + d=ino->sb; + buf->f_bsize=d->blksiz; + buf->f_blocks=d->size; + buf->f_bfree=0; + buf->f_bused=-(d->maxdir*32+d->blksiz-1)/d->blksiz; + for (i=0; ialvSize; ++i) + { + int temp,j; + + temp = *(d->alv+i); + for (j=0; jsize) + { + if (1&temp) + { +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmStatFS: block %d allocated\n",(i*INTBITS+j)); +#endif + ++buf->f_bused; + } + else ++buf->f_bfree; + } + temp >>= 1; + } + } + buf->f_bavail=buf->f_bfree; + buf->f_files=d->maxdir; + buf->f_ffree=0; + for (i=0; imaxdir; ++i) + { + if (d->dir[i].status==(char)0xe5) ++buf->f_ffree; + } + buf->f_namelen=11; +} +/*}}}*/ +/* cpmUnlink -- unlink */ /*{{{*/ +int cpmUnlink(const struct cpmInode *dir, const char *fname) +{ + int user; + char name[8],extension[3]; + int extent; + struct cpmSuperBlock *drive; + + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + drive=dir->sb; + if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1; + if ((extent=findFileExtent(drive,user,name,extension,0,-1))==-1) return -1; + drive->dirtyDirectory=1; + drive->dir[extent].status=(char)0xe5; + do + { + drive->dir[extent].status=(char)0xe5; + } while ((extent=findFileExtent(drive,user,name,extension,extent+1,-1))>=0); + alvInit(drive); + return 0; +} +/*}}}*/ +/* cpmRename -- rename */ /*{{{*/ +int cpmRename(const struct cpmInode *dir, const char *old, const char *new) +{ + struct cpmSuperBlock *drive; + int extent; + int olduser; + char oldname[8], oldext[3]; + int newuser; + char newname[8], newext[3]; + + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + drive=dir->sb; + if (splitFilename(old,dir->sb->type, oldname, oldext,&olduser)==-1) return -1; + if (splitFilename(new,dir->sb->type, newname, newext,&newuser)==-1) return -1; + if ((extent=findFileExtent(drive,olduser,oldname,oldext,0,-1))==-1) return -1; + if (findFileExtent(drive,newuser,newname, newext,0,-1)!=-1) + { + boo="file already exists"; + return -1; + } + do + { + drive->dirtyDirectory=1; + drive->dir[extent].status=newuser; + memcpy7(drive->dir[extent].name, newname, 8); + memcpy7(drive->dir[extent].ext, newext, 3); + } while ((extent=findFileExtent(drive,olduser,oldname,oldext,extent+1,-1))!=-1); + return 0; +} +/*}}}*/ +/* cpmOpendir -- opendir */ /*{{{*/ +int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp) +{ + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + dirp->ino=dir; + dirp->pos=0; + dirp->mode=O_RDONLY; + return 0; +} +/*}}}*/ +/* cpmReaddir -- readdir */ /*{{{*/ +int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent) +{ + /* variables */ /*{{{*/ + struct PhysDirectoryEntry *cur=(struct PhysDirectoryEntry*)0; + char buf[2+8+1+3+1]; /* 00foobarxy.zzy\0 */ + char *bufp; + int hasext; + /*}}}*/ + + if (!(S_ISDIR(dir->ino->mode))) /* error: not a directory */ /*{{{*/ + { + boo="not a directory"; + return -1; + } + /*}}}*/ + while (1) + { + int i; + + if (dir->pos==0) /* first entry is . */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir; + ent->reclen=1; + strcpy(ent->name,"."); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + else if (dir->pos==1) /* next entry is .. */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir; + ent->reclen=2; + strcpy(ent->name,".."); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + else if (dir->pos==2) + { + if (dir->ino->sb->passwdLength) /* next entry is [passwd] */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir+1; + ent->reclen=8; + strcpy(ent->name,"[passwd]"); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + } + else if (dir->pos==3) + { + if (dir->ino->sb->labelLength) /* next entry is [label] */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir+2; + ent->reclen=7; + strcpy(ent->name,"[label]"); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + } + else if (dir->pos>=RESERVED_ENTRIES && dir->pos<(int)dir->ino->sb->maxdir+RESERVED_ENTRIES) + { + int first=dir->pos-RESERVED_ENTRIES; + + if ((cur=dir->ino->sb->dir+(dir->pos-RESERVED_ENTRIES))->status>=0 && cur->status<=(dir->ino->sb->type&CPMFS_HI_USER ? 31 : 15)) + { + /* determine first extent for the current file */ /*{{{*/ + for (i=0; iino->sb->maxdir; ++i) if (i!=(dir->pos-RESERVED_ENTRIES)) + { + if (isMatching(cur->status,cur->name,cur->ext,dir->ino->sb->dir[i].status,dir->ino->sb->dir[i].name,dir->ino->sb->dir[i].ext) && EXTENT(cur->extnol,cur->extnoh)>EXTENT(dir->ino->sb->dir[i].extnol,dir->ino->sb->dir[i].extnoh)) first=i; + } + /*}}}*/ + if (first==(dir->pos-RESERVED_ENTRIES)) + { + ent->ino=dir->pos-RESERVED_INODES; + /* convert file name to UNIX style */ /*{{{*/ + buf[0]='0'+cur->status/10; + buf[1]='0'+cur->status%10; + for (bufp=buf+2,i=0; i<8 && (cur->name[i]&0x7f)!=' '; ++i) *bufp++=tolower(cur->name[i]&0x7f); + for (hasext=0,i=0; i<3 && (cur->ext[i]&0x7f)!=' '; ++i) + { + if (!hasext) { *bufp++='.'; hasext=1; } + *bufp++=tolower(cur->ext[i]&0x7f); + } + *bufp='\0'; + /*}}}*/ + assert(bufp<=buf+sizeof(buf)); + ent->reclen=bufp-buf; + strcpy(ent->name,buf); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + } + } + else return 0; + ++dir->pos; + } +} +/*}}}*/ +/* cpmStat -- stat */ /*{{{*/ +void cpmStat(const struct cpmInode *ino, struct cpmStat *buf) +{ + buf->ino=ino->ino; + buf->mode=ino->mode; + buf->size=ino->size; + buf->atime=ino->atime; + buf->mtime=ino->mtime; + buf->ctime=ino->ctime; +} +/*}}}*/ +/* cpmOpen -- open */ /*{{{*/ +int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode) +{ + if (S_ISREG(ino->mode)) + { + if ((mode&O_WRONLY) && (ino->mode&0222)==0) + { + boo="permission denied"; + return -1; + } + file->pos=0; + file->ino=ino; + file->mode=mode; + return 0; + } + else + { + boo="not a regular file"; + return -1; + } +} +/*}}}*/ +/* cpmRead -- read */ /*{{{*/ +int cpmRead(struct cpmFile *file, char *buf, int count) +{ + int findext=1,findblock=1,extent=-1,block=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1; + int blocksize=file->ino->sb->blksiz; + int extcap; + + extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize; + if (extcap>16384) extcap=16384*file->ino->sb->extents; + if (file->ino->ino==(ino_t)file->ino->sb->maxdir+1) /* [passwd] */ /*{{{*/ + { + if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos; + if (count) memcpy(buf,file->ino->sb->passwd+file->pos,count); + file->pos+=count; +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmRead passwd: read %d bytes, now at position %ld\n",count,(long)file->pos); +#endif + return count; + } + /*}}}*/ + else if (file->ino->ino==(ino_t)file->ino->sb->maxdir+2) /* [label] */ /*{{{*/ + { + if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos; + if (count) memcpy(buf,file->ino->sb->label+file->pos,count); + file->pos+=count; +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmRead label: read %d bytes, now at position %ld\n",count,(long)file->pos); +#endif + return count; + } + /*}}}*/ + else while (count>0 && file->posino->size) + { + char buffer[16384]; + + if (findext) + { + extentno=file->pos/16384; + extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno); + nextextpos=(file->pos/extcap)*extcap+extcap; + findext=0; + findblock=1; + } + if (findblock) + { + if (extent!=-1) + { + int start,end,ptr; + + ptr=(file->pos%extcap)/blocksize; + if (file->ino->sb->size>=256) ptr*=2; + block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr]; + if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8; + if (block==0) + { + memset(buffer,0,blocksize); + } + else + { + start=(file->pos%blocksize)/file->ino->sb->secLength; + end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength; + if (readBlock(file->ino->sb,block,buffer,start,end)==-1) + { + if (got==0) got=-1; + break; + } + } + } + nextblockpos=(file->pos/blocksize)*blocksize+blocksize; + findblock=0; + } + if (file->pospos%blocksize]; + ++file->pos; + ++got; + --count; + } + else if (file->pos==nextextpos) findext=1; else findblock=1; + } +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmRead: read %d bytes, now at position %ld\n",got,(long)file->pos); +#endif + return got; +} +/*}}}*/ +/* cpmWrite -- write */ /*{{{*/ +int cpmWrite(struct cpmFile *file, const char *buf, int count) +{ + int findext=1,findblock=-1,extent=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1; + int blocksize=file->ino->sb->blksiz; + int extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize; + int block=-1,start=-1,end=-1,ptr=-1,last=-1; + char buffer[16384]; + + while (count>0) + { + if (findext) /*{{{*/ + { + extentno=file->pos/16384; + extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno); + nextextpos=(file->pos/extcap)*extcap+extcap; + if (extent==-1) + { + if ((extent=findFreeExtent(file->ino->sb))==-1) return (got==0 ? -1 : got); + file->ino->sb->dir[extent]=file->ino->sb->dir[file->ino->ino]; + memset(file->ino->sb->dir[extent].pointers,0,16); + file->ino->sb->dir[extent].extnol=EXTENTL(extentno); + file->ino->sb->dir[extent].extnoh=EXTENTH(extentno); + file->ino->sb->dir[extent].blkcnt=0; + file->ino->sb->dir[extent].lrc=0; + time(&file->ino->ctime); + updateTimeStamps(file->ino,extent); + updateDsStamps(file->ino,extent); + } + findext=0; + findblock=1; + } + /*}}}*/ + if (findblock) /*{{{*/ + { + ptr=(file->pos%extcap)/blocksize; + if (file->ino->sb->size>=256) ptr*=2; + block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr]; + if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8; + if (block==0) /* allocate new block, set start/end to cover it */ /*{{{*/ + { + if ((block=allocBlock(file->ino->sb))==-1) return (got==0 ? -1 : got); + file->ino->sb->dir[extent].pointers[ptr]=block&0xff; + if (file->ino->sb->size>=256) file->ino->sb->dir[extent].pointers[ptr+1]=(block>>8)&0xff; + start=0; + end=(blocksize-1)/file->ino->sb->secLength; + memset(buffer,0,blocksize); + time(&file->ino->ctime); + updateTimeStamps(file->ino,extent); + updateDsStamps(file->ino,extent); + } + /*}}}*/ + else /* read existing block and set start/end to cover modified parts */ /*{{{*/ + { + start=(file->pos%blocksize)/file->ino->sb->secLength; + end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength; + if (file->pos%file->ino->sb->secLength) + { + if (readBlock(file->ino->sb,block,buffer,start,start)==-1) + { + if (got==0) got=-1; + break; + } + } + if (end!=start && (file->pos+count-1)ino->sb,block,buffer+end*file->ino->sb->secLength,end,end)==-1) + { + if (got==0) got=-1; + break; + } + } + } + /*}}}*/ + nextblockpos=(file->pos/blocksize)*blocksize+blocksize; + findblock=0; + } + /*}}}*/ + /* fill block and write it */ /*{{{*/ + file->ino->sb->dirtyDirectory=1; + while (file->pos!=nextblockpos && count) + { + buffer[file->pos%blocksize]=*buf++; + ++file->pos; + if (file->ino->sizepos) file->ino->size=file->pos; + ++got; + --count; + } + (void)writeBlock(file->ino->sb,block,buffer,start,end); + time(&file->ino->mtime); + if (file->ino->sb->size<256) for (last=15; last>=0; --last) + { + if (file->ino->sb->dir[extent].pointers[last]) + { + break; + } + } + else for (last=14; last>0; last-=2) + { + if (file->ino->sb->dir[extent].pointers[last] || file->ino->sb->dir[extent].pointers[last+1]) + { + last/=2; + break; + } + } + if (last>0) extentno+=(last*blocksize)/extcap; + file->ino->sb->dir[extent].extnol=EXTENTL(extentno); + file->ino->sb->dir[extent].extnoh=EXTENTH(extentno); + file->ino->sb->dir[extent].blkcnt=((file->pos-1)%16384)/128+1; + if (file->ino->sb->type & CPMFS_EXACT_SIZE) + { + file->ino->sb->dir[extent].lrc = (128 - (file->pos%128)) & 0x7F; + } + else + { + file->ino->sb->dir[extent].lrc=file->pos%128; + } + updateTimeStamps(file->ino,extent); + updateDsStamps(file->ino,extent); + /*}}}*/ + if (file->pos==nextextpos) findext=1; + else if (file->pos==nextblockpos) findblock=1; + } + return got; +} +/*}}}*/ +/* cpmClose -- close */ /*{{{*/ +int cpmClose(struct cpmFile *file) +{ + return 0; +} +/*}}}*/ +/* cpmCreat -- creat */ /*{{{*/ +int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode) +{ + int user; + char name[8],extension[3]; + int extent; + struct cpmSuperBlock *drive; + struct PhysDirectoryEntry *ent; + + if (!S_ISDIR(dir->mode)) + { + boo="No such file or directory"; + return -1; + } + if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1; +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmCreat: %s -> %d:%-.8s.%-.3s\n",fname,user,name,extension); +#endif + if (findFileExtent(dir->sb,user,name,extension,0,-1)!=-1) return -1; + drive=dir->sb; + if ((extent=findFreeExtent(dir->sb))==-1) return -1; + ent=dir->sb->dir+extent; + drive->dirtyDirectory=1; + memset(ent,0,32); + ent->status=user; + memcpy(ent->name,name,8); + memcpy(ent->ext,extension,3); + ino->ino=extent; + ino->mode=s_ifreg|mode; + ino->size=0; + + time(&ino->atime); + time(&ino->mtime); + time(&ino->ctime); + ino->sb=dir->sb; + updateTimeStamps(ino,extent); + updateDsStamps(ino,extent); + return 0; +} +/*}}}*/ + +/* cpmAttrGet -- get CP/M attributes */ /*{{{*/ +int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib) +{ + *attrib = ino->attr; + return 0; +} +/*}}}*/ +/* cpmAttrSet -- set CP/M attributes */ /*{{{*/ +int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib) +{ + struct cpmSuperBlock *drive; + int extent; + int user; + char name[8], extension[3]; + + memset(name, 0, sizeof(name)); + memset(extension, 0, sizeof(extension)); + drive = ino->sb; + extent = ino->ino; + + drive->dirtyDirectory=1; + /* Strip off existing attribute bits */ + memcpy7(name, drive->dir[extent].name, 8); + memcpy7(extension, drive->dir[extent].ext, 3); + user = drive->dir[extent].status; + + /* And set new ones */ + if (attrib & CPM_ATTR_F1) name[0] |= 0x80; + if (attrib & CPM_ATTR_F2) name[1] |= 0x80; + if (attrib & CPM_ATTR_F3) name[2] |= 0x80; + if (attrib & CPM_ATTR_F4) name[3] |= 0x80; + if (attrib & CPM_ATTR_RO) extension[0] |= 0x80; + if (attrib & CPM_ATTR_SYS) extension[1] |= 0x80; + if (attrib & CPM_ATTR_ARCV) extension[2] |= 0x80; + + do + { + memcpy(drive->dir[extent].name, name, 8); + memcpy(drive->dir[extent].ext, extension, 3); + } while ((extent=findFileExtent(drive, user,name,extension,extent+1,-1))!=-1); + + /* Update the stored (inode) copies of the file attributes and mode */ + ino->attr=attrib; + if (attrib&CPM_ATTR_RO) ino->mode&=~(S_IWUSR|S_IWGRP|S_IWOTH); + else ino->mode|=(S_IWUSR|S_IWGRP|S_IWOTH); + + return 0; +} +/*}}}*/ +/* cpmChmod -- set CP/M r/o & sys */ /*{{{*/ +int cpmChmod(struct cpmInode *ino, mode_t mode) +{ + /* Convert the chmod() into a chattr() call that affects RO */ + int newatt = ino->attr & ~CPM_ATTR_RO; + + if (!(mode & (S_IWUSR|S_IWGRP|S_IWOTH))) newatt |= CPM_ATTR_RO; + return cpmAttrSet(ino, newatt); +} +/*}}}*/ +/* cpmUtime -- set timestamps */ /*{{{*/ +void cpmUtime(struct cpmInode *ino, struct utimbuf *times) +{ + ino->atime = times->actime; + ino->mtime = times->modtime; + time(&ino->ctime); + updateTimeStamps(ino,ino->ino); + updateDsStamps(ino,ino->ino); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/cpmfs.h b/Tools/unix/cpmtools/cpmfs.h new file mode 100644 index 00000000..06126c86 --- /dev/null +++ b/Tools/unix/cpmtools/cpmfs.h @@ -0,0 +1,206 @@ +#ifndef CPMFS_H +#define CPMFS_H + +#include +#include +#include + +#ifdef _WIN32 + #include + #include + /* To make it compile on NT: extracts from Linux 2.0 * + * and */ + #define __S_IFMT 0170000 /* These bits determine file type. */ + #define __S_IFDIR 0040000 /* Directory. */ + #define __S_IFREG 0100000 /* Regular file. */ + #define __S_IWUSR 0000200 /* Writable for user. */ + #define __S_IWGRP 0000200 /* Writable for group. */ + #define __S_IWOTH 0000200 /* Writable for others. */ + + #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask)) + #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask)) + /* These bits are defined in Borland C++ 5 but not in MS Visual C++ */ + #ifndef S_ISDIR + # define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR) + #endif + #ifndef S_ISREG + # define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG) + #endif + #ifndef S_IWUSR + #define S_IWUSR __S_IWUSR + #endif + #ifndef S_IWGRP + #define S_IWGRP __S_IWGRP + #endif + #ifndef S_IWOTH + #define S_IWOTH __S_IWOTH + #endif + + #include /* For open(), lseek() etc. */ + #ifndef HAVE_MODE_T + typedef int mode_t; + #endif +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +#include "device.h" + +/* CP/M file attributes */ +#define CPM_ATTR_F1 1 +#define CPM_ATTR_F2 2 +#define CPM_ATTR_F3 4 +#define CPM_ATTR_F4 8 +/* F5-F8 are banned in CP/M 2 & 3, F7 is used by ZSDOS */ +#define CPM_ATTR_RO 256 /* Read-only */ +#define CPM_ATTR_SYS 512 /* System */ +#define CPM_ATTR_ARCV 1024 /* Archive */ +#define CPM_ATTR_PWDEL 2048 /* Password required to delete */ +#define CPM_ATTR_PWWRITE 4096 /* Password required to write */ +#define CPM_ATTR_PWREAD 8192 /* Password required to read */ + +typedef int cpm_attr_t; + +struct cpmInode +{ + ino_t ino; + mode_t mode; + off_t size; + cpm_attr_t attr; + time_t atime; + time_t mtime; + time_t ctime; + struct cpmSuperBlock *sb; +}; + +struct cpmFile +{ + mode_t mode; + off_t pos; + struct cpmInode *ino; +}; + +struct cpmDirent +{ + ino_t ino; + off_t off; + size_t reclen; + char name[2+8+1+3+1]; /* 00foobarxy.zzy\0 */ +}; + +struct cpmStat +{ + ino_t ino; + mode_t mode; + off_t size; + time_t atime; + time_t mtime; + time_t ctime; +}; + +#define CPMFS_HI_USER (0x1<<0) /* has user numbers up to 31 */ +#define CPMFS_CPM3_DATES (0x1<<1) /* has CP/M+ style time stamps */ +#define CPMFS_CPM3_OTHER (0x1<<2) /* has passwords and disc label */ +#define CPMFS_DS_DATES (0x1<<3) /* has datestamper timestamps */ +#define CPMFS_EXACT_SIZE (0x1<<4) /* has reverse exact file size */ + +#define CPMFS_DR22 0 +#define CPMFS_P2DOS (CPMFS_CPM3_DATES|CPMFS_HI_USER) +#define CPMFS_DR3 (CPMFS_CPM3_DATES|CPMFS_CPM3_OTHER|CPMFS_HI_USER) +#define CPMFS_ISX (CPMFS_EXACT_SIZE) +#define CPMFS_ZSYS (CPMFS_HI_USER) + +struct dsEntry +{ + char year; + char month; + char day; + char hour; + char minute; +}; + +struct dsDate +{ + struct dsEntry create; + struct dsEntry access; + struct dsEntry modify; + char checksum; +}; + +struct cpmSuperBlock +{ + struct Device dev; + + int secLength; + int tracks; + int sectrk; + int blksiz; + int maxdir; + int skew; + int boottrk; + off_t offset; + int type; + int size; + int extents; /* logical extents per physical extent */ + struct PhysDirectoryEntry *dir; + int alvSize; + int *alv; + int *skewtab; + int cnotatime; + char *label; + size_t labelLength; + char *passwd; + size_t passwdLength; + struct cpmInode *root; + int dirtyDirectory; + struct dsDate *ds; + int dirtyDs; + char libdskGeometry[256]; +}; + +struct cpmStatFS +{ + long f_bsize; + long f_blocks; + long f_bfree; + long f_bused; + long f_bavail; + long f_files; + long f_ffree; + long f_namelen; +}; + +extern const char cmd[]; +extern const char *boo; + +int match(const char *a, const char *pattern); +void cpmglob(int opti, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv); + +int cpmReadSuper(struct cpmSuperBlock *drive, struct cpmInode *root, const char *format); +int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i); +void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf); +int cpmUnlink(const struct cpmInode *dir, const char *fname); +int cpmRename(const struct cpmInode *dir, const char *old, const char *newname); +int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp); +int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent); +void cpmStat(const struct cpmInode *ino, struct cpmStat *buf); +int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib); +int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib); +int cpmChmod(struct cpmInode *ino, mode_t mode); +int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode); +int cpmRead(struct cpmFile *file, char *buf, int count); +int cpmWrite(struct cpmFile *file, const char *buf, int count); +int cpmClose(struct cpmFile *file); +int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode); +void cpmUtime(struct cpmInode *ino, struct utimbuf *times); +int cpmSync(struct cpmSuperBlock *sb); +void cpmUmount(struct cpmSuperBlock *sb); +int cpmCheckDs(struct cpmSuperBlock *sb); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Tools/unix/cpmtools/cpmls.1 b/Tools/unix/cpmtools/cpmls.1 new file mode 100644 index 00000000..91326400 --- /dev/null +++ b/Tools/unix/cpmtools/cpmls.1 @@ -0,0 +1,75 @@ +.TH CPMLS 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME \"{{{roff}}}\"{{{ +cpmls \- list sorted contents of directory +.\"}}} +.SH SYNOPSIS \"{{{ +.ad l +.B cpmls +.RB [ \-f +.IR format ] +.RB [ \-T +.IR libdsk-type ] +.RB [ \-d | \-D | \-F | \-A | \-l [ \-c ][ \-i ]] +.I image +.RI [ file-pattern "...]" +.ad b +.\"}}} +.SH DESCRIPTION \"{{{ +\fBCpmls\fP lists the sorted contents of the directory. +.\"}}} +.SH OPTIONS \"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.IP \fB\-d\fP +Old CP/M 2.2 dir output. +.IP \fB\-D\fP +P2DOS 2.3 ddir-like output. +.IP \fB\-F\fp +CP/M 3.x dir output. +.IP \fB\-A\fp +E2fs lsattr-like output. +.IP \fB\-l\fP +Long UNIX-style directory listing including size, time stamp and user number. +.IP \fB\-c\fP +Output the creation time, not the modification time. +.IP \fB\-i\fP +Print index number of each file. +.\"}}} +.SH "RETURN VALUE" \"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS \"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES \"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmcp (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmls.c b/Tools/unix/cpmtools/cpmls.c new file mode 100644 index 00000000..3721d2e0 --- /dev/null +++ b/Tools/unix/cpmtools/cpmls.c @@ -0,0 +1,400 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +/* variables */ /*{{{*/ +static const char * const month[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; +/*}}}*/ + +/* namecmp -- compare two entries */ /*{{{*/ +static int namecmp(const void *a, const void *b) +{ + if (**((const char * const *)a)=='[') return -1; + return strcmp(*((const char * const *)a),*((const char * const *)b)); +} +/*}}}*/ +/* olddir -- old style output */ /*{{{*/ +static void olddir(char **dirent, int entries) +{ + int i,j,k,l,user,announce; + + announce=0; + for (user=0; user<32; ++user) + { + for (i=l=0; itm_mday,month[tmp->tm_mon],tmp->tm_year+1900,tmp->tm_hour,tmp->tm_min); + } + else if (statbuf.ctime) printf(" "); + if (statbuf.ctime) + { + tmp=localtime(&statbuf.ctime); + printf(" %02d-%s-%04d %02d:%02d",tmp->tm_mday,month[tmp->tm_mon],tmp->tm_year+1900,tmp->tm_hour,tmp->tm_min); + } + putchar('\n'); + ++l; + } + } + if (announce==2) announce=1; + } + printf("%5.1d Files occupying %6.1ldK",l,(buf.f_bused*buf.f_bsize)/1024); + printf(", %7.1ldK Free.\n",(buf.f_bfree*buf.f_bsize)/1024); + } + else printf("No files found\n"); +} +/*}}}*/ +/* old3dir -- old CP/M Plus style long output */ /*{{{*/ +static void old3dir(char **dirent, int entries, struct cpmInode *ino) +{ + struct cpmStatFS buf; + struct cpmStat statbuf; + struct cpmInode file; + + if (entries) + { + int i,j,k,l,announce,user, attrib; + int totalBytes=0,totalRecs=0; + + qsort(dirent,entries,sizeof(char*),namecmp); + cpmStatFS(ino,&buf); + announce=1; + for (l=0,user=0; user<32; ++user) + { + for (i=0; itm_mon+1,tmp->tm_mday,tmp->tm_year%100,tmp->tm_hour,tmp->tm_min); + } + else if (statbuf.ctime) printf(" "); + if (statbuf.ctime) + { + tmp=localtime(&statbuf.ctime); + printf("%02d/%02d/%02d %02d:%02d",tmp->tm_mon+1,tmp->tm_mday,tmp->tm_year%100,tmp->tm_hour,tmp->tm_min); + } + putchar('\n'); + ++l; + } + } + if (announce==2) announce=1; + } + printf("\nTotal Bytes = %6.1dk ",(totalBytes+1023)/1024); + printf("Total Records = %7.1d ",totalRecs); + printf("Files Found = %4.1d\n",l); + printf("Total 1k Blocks = %6.1ld ",(buf.f_bused*buf.f_bsize)/1024); + printf("Used/Max Dir Entries For Drive A: %4.1ld/%4.1ld\n",buf.f_files-buf.f_ffree,buf.f_files); + } + else printf("No files found\n"); +} +/*}}}*/ +/* ls -- UNIX style output */ /*{{{*/ +static void ls(char **dirent, int entries, struct cpmInode *ino, int l, int c, int iflag) +{ + int i,user,announce,any; + time_t now; + struct cpmStat statbuf; + struct cpmInode file; + + time(&now); + qsort(dirent,entries,sizeof(char*),namecmp); + announce=0; + any=0; + for (user=0; user<32; ++user) + { + announce=0; + for (i=0; itm_mon],tmp->tm_mday); + if ((c ? statbuf.ctime : statbuf.mtime)<(now-182*24*3600)) printf("%04d ",tmp->tm_year+1900); + else printf("%02d:%02d ",tmp->tm_hour,tmp->tm_min); + } + printf("%s\n",dirent[i]+2); + } + } + } +} +/*}}}*/ +/* lsattr -- output something like e2fs lsattr */ /*{{{*/ +static void lsattr(char **dirent, int entries, struct cpmInode *ino) +{ + int i,user,announce,any; + struct cpmStat statbuf; + struct cpmInode file; + cpm_attr_t attrib; + + qsort(dirent,entries,sizeof(char*),namecmp); + announce=0; + any=0; + for (user=0; user<32; ++user) + { + announce=0; + for (i=0; i. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmrm.c b/Tools/unix/cpmtools/cpmrm.c new file mode 100644 index 00000000..65430da6 --- /dev/null +++ b/Tools/unix/cpmtools/cpmrm.c @@ -0,0 +1,77 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmrm"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,i,usage=0,exitcode=0; + struct cpmSuperBlock drive; + struct cpmInode root; + int gargc; + char **gargv; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'T': devopts=optarg; break; + case 'f': format=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind>=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-T dsktype] image pattern ...\n",cmd); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev, image, O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + cpmglob(optind,argc,argv,&root,&gargc,&gargv); + for (i=0; i +#include +#include +#include + +#include "device.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +static const char *lookupFormat(DSK_GEOMETRY *geom, const char *name) +{ + dsk_format_t fmt = FMT_180K; + const char *fname; + + while (dg_stdformat(NULL, fmt, &fname, NULL) == DSK_ERR_OK) + { + if (!strcmp(name, fname)) + { + dg_stdformat(geom, fmt, &fname, NULL); + return NULL; + } + ++fmt; + } + return "Unrecognised LibDsk geometry specification"; +} + +/* Device_open -- Open an image file */ /*{{{*/ +const char *Device_open(struct Device *this, const char *filename, int mode, const char *deviceOpts) +{ + char *format; + char driverName[80]; + const char *boo; + dsk_err_t e; + + /* Assume driver name & format name both fit in 80 characters, rather than + * malloccing the exact size */ + if (deviceOpts == NULL) + { + e = dsk_open(&this->dev, filename, NULL, NULL); + format = NULL; + } + else + { + strncpy(driverName, deviceOpts, 79); + driverName[79] = 0; + format = strchr(driverName, ','); + if (format) + { + *format = 0; + ++format; + } + e = dsk_open(&this->dev, filename, driverName, NULL); + } + this->opened = 0; + if (e) return dsk_strerror(e); + this->opened = 1; + if (format) + { + boo = lookupFormat(&this->geom, format); + if (boo) return boo; + } + else + { + dsk_getgeom(this->dev, &this->geom); + } + return NULL; +} +/*}}}*/ +/* Device_setGeometry -- Set disk geometry */ /*{{{*/ +const char *Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry) +{ + char *boo; + + this->secLength=secLength; + this->sectrk=sectrk; + this->tracks=tracks; + /* Must be an even multiple of sector size */ + assert(offset%secLength==0); + this->offset=offset; + /* If a geometry is named in diskdefs, use it */ + if (libdskGeometry && libdskGeometry[0]) + { + return lookupFormat(&this->geom, libdskGeometry); + } + + this->geom.dg_secsize = secLength; + this->geom.dg_sectors = sectrk; + /* Did the autoprobe guess right about the number of sectors & cylinders? */ + if (this->geom.dg_cylinders * this->geom.dg_heads == tracks) return NULL; + /* Otherwise we guess: <= 43 tracks: single-sided. Else double. This + * fails for 80-track single-sided if there are any such beasts */ + if (tracks <= 43) + { + this->geom.dg_cylinders = tracks; + this->geom.dg_heads = 1; + } + else + { + this->geom.dg_cylinders = tracks/2; + this->geom.dg_heads = 2; + } + return NULL; +} +/*}}}*/ +/* Device_close -- Close an image file */ /*{{{*/ +const char *Device_close(struct Device *this) +{ + dsk_err_t e; + this->opened=0; + e = dsk_close(&this->dev); + return (e?dsk_strerror(e):(const char*)0); +} +/*}}}*/ +/* Device_readSector -- read a physical sector */ /*{{{*/ +const char *Device_readSector(const struct Device *this, int track, int sector, char *buf) +{ + dsk_err_t e; + e = dsk_lread(this->dev, &this->geom, buf, (track * this->sectrk) + sector + this->offset/this->secLength); + return (e?dsk_strerror(e):(const char*)0); +} +/*}}}*/ +/* Device_writeSector -- write physical sector */ /*{{{*/ +const char *Device_writeSector(const struct Device *this, int track, int sector, const char *buf) +{ + dsk_err_t e; + e = dsk_lwrite(this->dev, &this->geom, buf, (track * this->sectrk) + sector + this->offset/this->secLength); + return (e?dsk_strerror(e):(const char*)0); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/device_posix.c b/Tools/unix/cpmtools/device_posix.c new file mode 100644 index 00000000..5a28dcdc --- /dev/null +++ b/Tools/unix/cpmtools/device_posix.c @@ -0,0 +1,82 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "device.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +/* Device_open -- Open an image file */ /*{{{*/ +const char *Device_open(struct Device *this, const char *filename, int mode, const char *deviceOpts) +{ + this->fd=open(filename,mode); + this->opened=(this->fd==-1?0:1); + return ((this->fd==-1)?strerror(errno):(const char*)0); +} +/*}}}*/ +/* Device_setGeometry -- Set disk geometry */ /*{{{*/ +const char *Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry) +{ + this->secLength=secLength; + this->sectrk=sectrk; + this->tracks=tracks; + this->offset=offset; + return NULL; +} +/*}}}*/ +/* Device_close -- Close an image file */ /*{{{*/ +const char *Device_close(struct Device *this) +{ + this->opened=0; + return ((close(this->fd)==-1)?strerror(errno):(const char*)0); +} +/*}}}*/ +/* Device_readSector -- read a physical sector */ /*{{{*/ +const char *Device_readSector(const struct Device *this, int track, int sector, char *buf) +{ + int res; + + assert(this); + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + assert(buf); + if (lseek(this->fd,(off_t)(((sector+track*this->sectrk)*this->secLength)+this->offset),SEEK_SET)==-1) + { + return strerror(errno); + } + if ((res=read(this->fd, buf, this->secLength)) != this->secLength) + { + if (res==-1) + { + return strerror(errno); + } + else memset(buf+res,0,this->secLength-res); /* hit end of disk image */ + } + return (const char*)0; +} +/*}}}*/ +/* Device_writeSector -- write physical sector */ /*{{{*/ +const char *Device_writeSector(const struct Device *this, int track, int sector, const char *buf) +{ + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + if (lseek(this->fd,(off_t)(((sector+track*this->sectrk)*this->secLength)+this->offset),SEEK_SET)==-1) + { + return strerror(errno); + } + if (write(this->fd, buf, this->secLength) == this->secLength) return (const char*)0; + return strerror(errno); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/device_win32.c b/Tools/unix/cpmtools/device_win32.c new file mode 100644 index 00000000..18294432 --- /dev/null +++ b/Tools/unix/cpmtools/device_win32.c @@ -0,0 +1,670 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include + +#include "cpmdir.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* types */ /*{{{*/ +#define PHYSICAL_SECTOR_1 1 /* First physical sector */ + +/* Use the INT13 interface rather than INT25/INT26. This appears to + * improve performance, but is less well tested. */ +#define USE_INT13 + +/* Windows 95 disk I/O functions - based on Stan Mitchell's DISKDUMP.C */ +#define VWIN32_DIOC_DOS_IOCTL 1 /* DOS ioctl calls 4400h-4411h */ +#define VWIN32_DIOC_DOS_INT25 2 /* absolute disk read, DOS int 25h */ +#define VWIN32_DIOC_DOS_INT26 3 /* absolute disk write, DOS int 26h */ +#define VWIN32_DIOC_DOS_INT13 4 /* BIOS INT13 functions */ + +typedef struct _DIOC_REGISTERS { + DWORD reg_EBX; + DWORD reg_EDX; + DWORD reg_ECX; + DWORD reg_EAX; + DWORD reg_EDI; + DWORD reg_ESI; + DWORD reg_Flags; + } + DIOC_REGISTERS, *PDIOC_REGISTERS; + +#define LEVEL0_LOCK 0 +#define LEVEL1_LOCK 1 +#define LEVEL2_LOCK 2 +#define LEVEL3_LOCK 3 +#define LEVEL1_LOCK_MAX_PERMISSION 0x0001 + +#define DRIVE_IS_REMOTE 0x1000 +#define DRIVE_IS_SUBST 0x8000 + +/********************************************************* + **** Note: all MS-DOS data structures must be packed **** + **** on a one-byte boundary. **** + *********************************************************/ +#pragma pack(1) + +typedef struct _DISKIO { + DWORD diStartSector; /* sector number to start at */ + WORD diSectors; /* number of sectors */ + DWORD diBuffer; /* address of buffer */ + } + DISKIO, *PDISKIO; + +typedef struct MID { + WORD midInfoLevel; /* information level, must be 0 */ + DWORD midSerialNum; /* serial number for the medium */ + char midVolLabel[11]; /* volume label for the medium */ + char midFileSysType[8]; /* type of file system as 8-byte ASCII */ + } + MID, *PMID; + +typedef struct driveparams { /* Disk geometry */ + BYTE special; + BYTE devicetype; + WORD deviceattrs; + WORD cylinders; + BYTE mediatype; + /* BPB starts here */ + WORD bytespersector; + BYTE sectorspercluster; + WORD reservedsectors; + BYTE numberofFATs; + WORD rootdirsize; + WORD totalsectors; + BYTE mediaid; + WORD sectorsperfat; + WORD sectorspertrack; + WORD heads; + DWORD hiddensectors; + DWORD bigtotalsectors; + BYTE reserved[6]; + /* BPB ends here */ + WORD sectorcount; + WORD sectortable[80]; + } DRIVEPARAMS, *PDRIVEPARAMS; +/*}}}*/ + +static char *strwin32error(void) /*{{{*/ +{ + static char buffer[1024]; + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ + (LPTSTR)buffer, + 1023, NULL); + return buffer; +} +/*}}}*/ +static BOOL LockVolume( HANDLE hDisk ) /*{{{*/ +{ + DWORD ReturnedByteCount; + + return DeviceIoControl( hDisk, FSCTL_LOCK_VOLUME, NULL, 0, NULL, + 0, &ReturnedByteCount, NULL ); +} +/*}}}*/ +static BOOL UnlockVolume( HANDLE hDisk ) /*{{{*/ +{ + DWORD ReturnedByteCount; + + return DeviceIoControl( hDisk, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, + 0, &ReturnedByteCount, NULL ); +} +/*}}}*/ +static BOOL DismountVolume( HANDLE hDisk ) /*{{{*/ +{ + DWORD ReturnedByteCount; + + return DeviceIoControl( hDisk, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, + 0, &ReturnedByteCount, NULL ); +} +/*}}}*/ +static int GetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/ + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* IOCTL for block device */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_ECX = 0x0860; /* Get Device params */ + reg.reg_EDX = (DWORD)pParam; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; + } +/*}}}*/ +static int SetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/ + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* IOCTL for block device */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_ECX = 0x0840; /* Set Device params */ + reg.reg_EDX = (DWORD)pParam; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; + } +/*}}}*/ +static int GetMediaID( HANDLE hVWin32Device, int volume, MID* pMid ) /*{{{*/ + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* IOCTL for block device */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_ECX = 0x0866; /* Get Media ID */ + reg.reg_EDX = (DWORD)pMid; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; + } +/*}}}*/ +static int VolumeCheck(HANDLE hVWin32Device, int volume, WORD* flags ) /*{{{*/ +{ + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x4409; /* Is Drive Remote */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + *flags = (WORD)(reg.reg_EDX & 0xffff); + return 0; +} +/*}}}*/ +static int LockLogicalVolume(HANDLE hVWin32Device, int volume, int lock_level, int permissions) /*{{{*/ +{ + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* generic IOCTL */ + reg.reg_ECX = 0x084a; /* lock logical volume */ + reg.reg_EBX = volume | (lock_level << 8); + reg.reg_EDX = permissions; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; +} +/*}}}*/ +static int UnlockLogicalVolume( HANDLE hVWin32Device, int volume ) /*{{{*/ +{ + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; + reg.reg_ECX = 0x086a; /* lock logical volume */ + reg.reg_EBX = volume; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) return -1; + return 0; +} +/*}}}*/ +static int w32mode(int mode) /*{{{*/ +{ + switch(mode) + { + case O_RDONLY: return GENERIC_READ; + case O_WRONLY: return GENERIC_WRITE; + } + return GENERIC_READ | GENERIC_WRITE; +} +/*}}}*/ + +/* Device_open -- Open an image file */ /*{{{*/ +const char *Device_open(struct Device *sb, const char *filename, int mode, const char *deviceOpts) +{ + /* Windows 95/NT: floppy drives using handles */ + if (strlen(filename) == 2 && filename[1] == ':') /* Drive name */ + { + char vname[20]; + DWORD dwVers; + + sb->fd = -1; + dwVers = GetVersion(); + + if (dwVers & 0x80000000L) /* Win32s (3.1) or Win32c (Win95) */ + { + int lock, driveno, res, permissions; + unsigned short drive_flags; + MID media; + + vname[0] = toupper(filename[0]); + driveno = vname[0] - 'A' + 1; /* 1=A: 2=B: */ + sb->drvtype = CPMDRV_WIN95; + sb->hdisk = CreateFile( "\\\\.\\vwin32", + 0, + 0, + NULL, + 0, + FILE_FLAG_DELETE_ON_CLOSE, + NULL ); + if (!sb->hdisk) + { + return "Failed to open VWIN32 driver."; + } + if (VolumeCheck(sb->hdisk, driveno, &drive_flags)) + { + CloseHandle(sb->hdisk); + return "Invalid drive"; + } + res = GetMediaID( sb->hdisk, driveno, &media ); + if ( res ) + { + const char *lboo = NULL; + + if ( res == ERROR_INVALID_FUNCTION && + (drive_flags & DRIVE_IS_REMOTE )) + lboo = "Network drive"; + else if (res == ERROR_ACCESS_DENIED) lboo = "Access denied"; + /* nb: It's perfectly legitimate for GetMediaID() to fail; most CP/M */ + /* CP/M disks won't have a media ID. */ + + if (lboo != NULL) + { + CloseHandle(sb->hdisk); + return lboo; + } + } + if (!res && + (!memcmp( media.midFileSysType, "CDROM", 5 ) || + !memcmp( media.midFileSysType, "CD001", 5 ) || + !memcmp( media.midFileSysType, "CDAUDIO", 5 ))) + { + CloseHandle(sb->hdisk); + return "CD-ROM drive"; + } + if (w32mode(mode) & GENERIC_WRITE) + { + lock = LEVEL0_LOCK; /* Exclusive access */ + permissions = 0; + } + else + { + lock = LEVEL1_LOCK; /* Allow other processes access */ + permissions = LEVEL1_LOCK_MAX_PERMISSION; + } + if (LockLogicalVolume( sb->hdisk, driveno, lock, permissions)) + { + CloseHandle(sb->hdisk); + return "Could not acquire a lock on the drive."; + } + + sb->fd = driveno; /* 1=A: 2=B: etc - we will need this later */ + + } + else + { + sprintf(vname, "\\\\.\\%s", filename); + sb->drvtype = CPMDRV_WINNT; + sb->hdisk = CreateFile(vname, /* Name */ + w32mode(mode), /* Access mode */ + FILE_SHARE_READ|FILE_SHARE_WRITE, /*Sharing*/ + NULL, /* Security attributes */ + OPEN_EXISTING, /* See MSDN */ + 0, /* Flags & attributes */ + NULL); /* Template file */ + + if (sb->hdisk != INVALID_HANDLE_VALUE) + { + sb->fd = 1; /* Arbitrary value >0 */ + if (LockVolume(sb->hdisk) == FALSE) /* Lock drive */ + { + char *lboo = strwin32error(); + CloseHandle(sb->hdisk); + sb->fd = -1; + return lboo; + } + } + else return strwin32error(); + } + sb->opened = 1; + return NULL; + } + + /* Not a floppy. Treat it as a normal file */ + + mode |= O_BINARY; + sb->fd = open(filename, mode); + if (sb->fd == -1) return strerror(errno); + sb->drvtype = CPMDRV_FILE; + sb->opened = 1; + return NULL; +} +/*}}}*/ +/* Device_setGeometry -- Set disk geometry */ /*{{{*/ +const char * Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry) +{ + int n; + + this->secLength=secLength; + this->sectrk=sectrk; + this->tracks=tracks; + // Bill Buckels - add this->offset + this->offset=offset; + + + // Bill Buckels - not sure what to do here + if (this->drvtype == CPMDRV_WIN95) + { + DRIVEPARAMS drvp; + memset(&drvp, 0, sizeof(drvp)); + if (GetDriveParams( this->hdisk, this->fd, &drvp )) return "GetDriveParams failed"; + + drvp.bytespersector = secLength; + drvp.sectorspertrack = sectrk; + drvp.totalsectors = sectrk * tracks; + +/* Guess the cylinder/head configuration from the track count. This will + * get single-sided 80-track discs wrong, but it's that or double-sided + * 40-track (or add cylinder/head counts to diskdefs) + */ + if (tracks < 44) + { + drvp.cylinders = tracks; + drvp.heads = 1; + } + else + { + drvp.cylinders = tracks / 2; + drvp.heads = 2; + } + +/* Set up "reasonable" values for the other members */ + + drvp.sectorspercluster = 1024 / secLength; + drvp.reservedsectors = 1; + drvp.numberofFATs = 2; + drvp.sectorcount = sectrk; + drvp.rootdirsize = 64; + drvp.mediaid = 0xF0; + drvp.hiddensectors = 0; + drvp.sectorsperfat = 3; + for (n = 0; n < sectrk; n++) + { + drvp.sectortable[n*2] = n + PHYSICAL_SECTOR_1; /* Physical sector numbers */ + drvp.sectortable[n*2+1] = secLength; + } + drvp.special = 6; +/* We have not set: + + drvp.mediatype + drvp.devicetype + drvp.deviceattrs + + which should have been read correctly by GetDriveParams(). + */ + SetDriveParams( this->hdisk, this->fd, &drvp ); + } + return NULL; +} +/*}}}*/ +/* Device_close -- Close an image file */ /*{{{*/ +const char *Device_close(struct Device *sb) +{ + sb->opened = 0; + switch(sb->drvtype) + { + case CPMDRV_WIN95: + UnlockLogicalVolume(sb->hdisk, sb->fd ); + if (!CloseHandle( sb->hdisk )) return strwin32error(); + return NULL; + + case CPMDRV_WINNT: + DismountVolume(sb->hdisk); + UnlockVolume(sb->hdisk); + if (!CloseHandle(sb->hdisk)) return strwin32error(); + return NULL; + } + if (close(sb->fd)) return strerror(errno); + return NULL; +} +/*}}}*/ +/* Device_readSector -- read a physical sector */ /*{{{*/ +const char *Device_readSector(const struct Device *drive, int track, int sector, char *buf) +{ + int res; + off_t offset; + + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + + offset = ((sector+track*drive->sectrk)*drive->secLength); + + if (drive->drvtype == CPMDRV_WINNT) + { + LPVOID iobuffer; + DWORD bytesread; + + // Bill Buckels - add drive->offset + if (SetFilePointer(drive->hdisk, offset+drive->offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE) + { + return strwin32error(); + } + iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE); + if (!iobuffer) + { + return strwin32error(); + } + res = ReadFile(drive->hdisk, iobuffer, drive->secLength, &bytesread, NULL); + if (!res) + { + char *lboo = strwin32error(); + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + return lboo; + } + + memcpy(buf, iobuffer, drive->secLength); + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + + if (bytesread < (unsigned)drive->secLength) + { + memset(buf + bytesread, 0, drive->secLength - bytesread); + } + return NULL; + } + + // Bill Buckels - not sure what to do here + if (drive->drvtype == CPMDRV_WIN95) + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + +#ifdef USE_INT13 + int cyl, head; + + if (drive->tracks < 44) { cyl = track; head = 0; } + else { cyl = track/2; head = track & 1; } + + reg.reg_EAX = 0x0201; /* Read 1 sector */ + reg.reg_EBX = (DWORD)buf; + reg.reg_ECX = (cyl << 8) | (sector + PHYSICAL_SECTOR_1); + reg.reg_EDX = (head << 8) | (drive->fd - 1); + reg.reg_Flags = 1; /* preset the carry flag */ + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); +#else + DISKIO di; + + reg.reg_EAX = drive->fd - 1; /* zero-based volume number */ + reg.reg_EBX = (DWORD)&di; + reg.reg_ECX = 0xffff; /* use DISKIO structure */ + reg.reg_Flags = 1; /* preset the carry flag */ + di.diStartSector = sector+track*drive->sectrk; + di.diSectors = 1; + di.diBuffer = (DWORD)buf; + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT25, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + +#endif + if ( !bResult || (reg.reg_Flags & 1) ) + { + if (GetLastError()) return strwin32error(); + return "Unknown read error."; + } + return 0; + } + + // Bill Buckels - add drive->offset + if (lseek(drive->fd,offset+drive->offset,SEEK_SET)==-1) + { + return strerror(errno); + } + if ((res=read(drive->fd, buf, drive->secLength)) != drive->secLength) + { + if (res==-1) + { + return strerror(errno); + } + else memset(buf+res,0,drive->secLength-res); /* hit end of disk image */ + } + return NULL; +} +/*}}}*/ +/* Device_writeSector -- write physical sector */ /*{{{*/ +const char *Device_writeSector(const struct Device *drive, int track, int sector, const char *buf) +{ + off_t offset; + int res; + + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + + offset = ((sector+track*drive->sectrk)*drive->secLength); + + if (drive->drvtype == CPMDRV_WINNT) + { + LPVOID iobuffer; + DWORD byteswritten; + + // Bill Buckels - add drive->offset + if (SetFilePointer(drive->hdisk, offset+drive->offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE) + { + return strwin32error(); + } + iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE); + if (!iobuffer) + { + return strwin32error(); + } + memcpy(iobuffer, buf, drive->secLength); + res = WriteFile(drive->hdisk, iobuffer, drive->secLength, &byteswritten, NULL); + if (!res || (byteswritten < (unsigned)drive->secLength)) + { + char *lboo = strwin32error(); + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + return lboo; + } + + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + return NULL; + } + + // Bill Buckels - not sure what to do here + if (drive->drvtype == CPMDRV_WIN95) + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + +#ifdef USE_INT13 + int cyl, head; + + if (drive->tracks < 44) { cyl = track; head = 0; } + else { cyl = track/2; head = track & 1; } + + reg.reg_EAX = 0x0301; /* Write 1 sector */ + reg.reg_EBX = (DWORD)buf; + reg.reg_ECX = (cyl << 8) | (sector + PHYSICAL_SECTOR_1); + reg.reg_EDX = (head << 8) | (drive->fd - 1); + reg.reg_Flags = 1; /* preset the carry flag */ + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); +#else + DISKIO di; + + reg.reg_EAX = drive->fd - 1; /* zero-based volume number */ + reg.reg_EBX = (DWORD)&di; + reg.reg_ECX = 0xffff; /* use DISKIO structure */ + reg.reg_Flags = 1; /* preset the carry flag */ + di.diStartSector = sector+track*drive->sectrk; + di.diSectors = 1; + di.diBuffer = (DWORD)buf; + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT26, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); +#endif + + if ( !bResult || (reg.reg_Flags & 1) ) + { + if (GetLastError()) return strwin32error(); + return "Unknown write error."; + } + return NULL; + } + + // Bill Buckels - add drive->offset + if (lseek(drive->fd,offset+drive->offset, SEEK_SET)==-1) + { + return strerror(errno); + } + if (write(drive->fd, buf, drive->secLength) == drive->secLength) return NULL; + return strerror(errno); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/diskdefs b/Tools/unix/cpmtools/diskdefs new file mode 100644 index 00000000..630317a0 --- /dev/null +++ b/Tools/unix/cpmtools/diskdefs @@ -0,0 +1,1396 @@ +diskdef ibm-3740 + seclen 128 + tracks 77 + sectrk 26 + blocksize 1024 + maxdir 64 + skew 6 + boottrk 2 + os 2.2 +end + +diskdef 4mb-hd + seclen 128 + tracks 1024 + sectrk 32 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 0 + os p2dos +end + +diskdef pcw + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 1 + os 3 + libdsk:format pcw180 +end + +diskdef pc1.2m + seclen 512 + tracks 80 + # this format uses 15 sectors per track, but 30 per cylinder + sectrk 30 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 0 + os 3 +end + +# CP/M 86 on 1.44MB floppies +diskdef cpm86-144feat + seclen 512 + tracks 160 + sectrk 18 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 3 + libdsk:format ibm1440 +end + +# CP/M 86 on 720KB floppies +diskdef cpm86-720 + seclen 512 + tracks 160 + sectrk 9 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 2 + os 3 +end + +diskdef cf2dd + seclen 512 + tracks 160 + sectrk 9 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 1 + os 3 + libdsk:format pcw720 +end + +#amstrad: values are read from super block (special name hardcoded) + +# Royal alphatronic +# setfdprm /dev/fd1 dd ssize=256 cyl=40 sect=16 head=2 +diskdef alpha + seclen 256 + tracks 40 + sectrk 32 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Apple II CP/M skew o Apple II DOS 3.3 skew +diskdef apple-do + seclen 256 + tracks 35 + sectrk 16 + blocksize 1024 + maxdir 64 + skewtab 0,6,12,3,9,15,14,5,11,2,8,7,13,4,10,1 + boottrk 3 + os 2.2 +end + +# Apple II CP/M skew o Apple II PRODOS skew +diskdef apple-po + seclen 256 + tracks 35 + sectrk 16 + blocksize 1024 + maxdir 64 + skewtab 0,9,3,12,6,15,1,10,4,13,7,8,2,11,5,14 + boottrk 3 + os 2.2 +end + +# MYZ80 hard drive (only works with libdsk, because it has a 256-byte header) +diskdef myz80 + seclen 1024 + tracks 64 + sectrk 128 + blocksize 4096 + maxdir 1024 + skew 1 + boottrk 0 + os 3 + libdsk:format pcw720 +end + +# Despite being Amstrad formats, CPC System and CPC Data don't have an Amstrad +# superblock. You'll need to use libdsk to access them because the Linux +# and Windows kernel drivers won't touch them. +diskdef cpcsys + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 2 + os 3 + libdsk:format cpcsys +end +diskdef cpcdata + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 0 + os 3 + libdsk:format cpcdata +end + +# after being read in with no sector skew. +diskdef nigdos + seclen 512 + # NigDos double sided disk format, 42 tracks * 2 sides + tracks 84 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 0 + # this format wastes half of the directory entry + logicalextents 1 + os 3 +end + +diskdef epsqx10 + seclen 512 + tracks 40 + sectrk 20 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef ibm-8ss + seclen 512 + tracks 40 + sectrk 8 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +diskdef ibm-8ds + seclen 512 + tracks 40 + sectrk 8 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +diskdef electroglas + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 1 + os 3 +end + +# IBM CP/M-86 +# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48 head=1 +diskdef ibmpc-514ss + seclen 512 + tracks 40 + sectrk 8 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 1 + os 2.2 + libdsk:format ibm160 +end + +# IBM CP/M-86 +# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48 +diskdef ibmpc-514ds + seclen 512 + tracks 80 + sectrk 8 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 2 + os 2.2 + libdsk:format ibm320 +end + +diskdef p112 + seclen 512 + tracks 160 + sectrk 18 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 2 + os 3 +end + +diskdef p112-old + seclen 512 + tracks 160 + sectrk 18 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 1 + os 3 +end + +diskdef gide-cfa + seclen 512 + tracks 1000 + sectrk 16 + blocksize 4096 + maxdir 1024 + skew 0 + boottrk 2 + os 3 +end + +diskdef gide-cfb + seclen 512 + tracks 1000 + sectrk 16 + blocksize 4096 + maxdir 1024 + skew 0 + boottrk 0 +# Start of second partition + offset 1000trk + os 3 +end + +# AT&T/Olivetti Word Processor +diskdef attwp + seclen 256 + tracks 80 + sectrk 32 + blocksize 2048 + maxdir 128 + boottrk 1 + logicalextents 1 + skewtab 0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15,16,18,20,22,24,26,28,30,17,19,21,23,25,27,29,31 + os 2.2 +end + +# setfdprm /dev/fd0 zerobased SS DD ssize=512 cyl=40 sect=10 head=1 +# Kaypro II +diskdef kpii + seclen 512 + tracks 40 + sectrk 10 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +# setfdprm /dev/fd0 zerobased DS DD ssize=512 cyl=40 sect=10 head=2 +# Kayro IV +diskdef kpiv + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +# setfdprm /dev/fd0 dd sect=10 +diskdef interak + seclen 512 + tracks 80 + sectrk 20 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 2.2 +end + +# Timex FDD3000 3" +diskdef fdd3000 + seclen 256 + tracks 40 + sectrk 16 + blocksize 1024 + maxdir 128 + boottrk 4 + os 2.2 + skew 7 +end + +# Timex FDD3000 3" +diskdef fdd3000_2 + seclen 256 + tracks 40 + sectrk 16 + blocksize 1024 + maxdir 128 + boottrk 2 + os 2.2 + skew 5 +end + +# Robotron 1715 +diskdef 1715 + seclen 1024 + tracks 40 + sectrk 5 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 3 + os 2.2 +end + +# Robotron 1715 with SCP3 +diskdef 17153 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 4 + os 3 +end + +#DDR +diskdef scp624 + seclen 256 + tracks 160 + sectrk 16 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef scp640 + seclen 256 + tracks 160 + sectrk 16 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 0 + os 2.2 +end + +diskdef scp780 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef scp800 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 0 + os 2.2 +end + +diskdef z9001 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 192 + skew 0 + boottrk 0 + os 2.2 +end + +# Visual Technology Visual 1050 computer +diskdef v1050 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 3 +end + +# Microbee 40 track 5.25" disks +diskdef microbee40 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skewtab 1,4,7,0,3,6,9,2,5,8 + boottrk 2 + os 2.2 +end + +diskdef dreamdisk40 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skewtab 1,4,7,0,3,6,9,2,5,8 + boottrk 2 + os 2.2 +end + +diskdef dreamdisk80 + seclen 512 + tracks 160 + sectrk 10 + blocksize 2048 + maxdir 256 + skewtab 1,4,7,0,3,6,9,2,5,8 + boottrk 2 + os 2.2 +end + +diskdef rc759 + seclen 1024 + tracks 154 + sectrk 8 + blocksize 2048 + maxdir 512 + boottrk 4 + os 3 +end + +# ICL Comet: 40 track 5.25" Single Sided +# +diskdef icl-comet-525ss + seclen 512 + tracks 40 + sectrk 10 + blocksize 1024 + maxdir 64 + skewtab 0,3,6,9,2,5,8,1,4,7 + boottrk 2 + os 2.2 +end + +diskdef z80pack-hd + seclen 128 + tracks 255 + sectrk 128 + blocksize 2048 + maxdir 1024 + skew 0 + boottrk 0 + os 2.2 +end + +diskdef z80pack-hdb + seclen 128 + tracks 256 + sectrk 16384 + blocksize 16384 + maxdir 8192 + skew 0 + boottrk 0 + os 2.2 +end + +# Bondwell 12 and 14 disk images in IMD raw binary format +diskdef bw12 + seclen 256 + tracks 40 + sectrk 18 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +diskdef bw14 + seclen 256 + tracks 80 + sectrk 18 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +############################ +# north star cp/m disks +############################ + +#North Star floppy 360K + +diskdef nsfd + seclen 512 + tracks 70 + sectrk 10 + blocksize 2048 + maxdir 64 + skew 5 + boottrk 2 + os 2.2 +end + + +#North Star CP/M Virtual-Disk file on Hard Disk +# prepared with allocation factor = 4 +# as in "CR CPMB 4000 4" +# needs to be copied off hard drive before you can +# work on it with cpmtools + +diskdef nshd4 + seclen 512 + tracks 512 + sectrk 16 + blocksize 4096 + maxdir 256 + skew 0 + boottrk 0 + os 2.2 +end + + +#North Star CP/M Virtual-Disk file on Hard Disk +# prepared with allocation factor = 8 +# as in "CR CPMB 6000 8" +# needs to be copied off hard drive before you can +# work on it with cpmtools + +diskdef nshd8 + seclen 512 + tracks 1024 + sectrk 16 + blocksize 8192 + maxdir 256 + skew 0 + boottrk 0 + os 2.2 +end + +# Northstar Micro-Disk System MDS-A-D 175 +diskdef mdsad175 + seclen 512 + blocksize 1024 + tracks 35 + maxdir 64 + boottrk 2 + sectrk 10 + skew 5 + os 2.2 +end + + +# Northstar Micro-Disk System MDS-A-D 350 +diskdef mdsad350 + seclen 512 + blocksize 2048 + tracks 70 + maxdir 64 + boottrk 2 + sectrk 10 + skew 5 + os 2.2 +end + + +# Osborne 1 +diskdef osborne1 + seclen 1024 + tracks 40 + sectrk 5 + blocksize 1024 + maxdir 64 + boottrk 3 + os 2.2 +end + +# Osborne Nuevo/Vixen/4 +diskdef osborne4 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 2 + boottrk 2 + os 2.2 +end + +# Lobo Max-80 8" CP/M 2 +diskdef lobo2 + seclen 256 + tracks 77 + sectrk 30 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 2 + os 2.2 +end + +#Lobo Max-80 8" CP/M 3 +diskdef lobo3 + seclen 512 + tracks 77 + sectrk 17 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 2 + os 3 +end + +# PRO CP/M RZ50 DZ format (Perhaps only 79 tracks should be used?) +diskdef dec_pro + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 2 + boottrk 2 + os 2.2 +end + +# TDOS with DateStamper +diskdef tdos-ds + seclen 1024 + tracks 77 + sectrk 16 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 1 + os zsys +end + +# The following entires are tested and working +# Most of the images are either from Don Maslin's archive or from +# Dave Dunfield's site, but not all - they are noted as well as +# their size. + +# PMC Micromate +# Dave Dunfield's Imagedisk information from DSK conversion from IMD: +# IMageDisk Utility 1.18 / Mar 07 2012 +# IMD 1.14: 10/03/2007 11:13:27 +# PMC-101 MicroMate +# CP/M Plus +# System Master +# Assuming 1:1 for Binary output +# 0/0 250 kbps DD 5x1024 +# 80 tracks(40/40), 400 sectors (12 Compressed) +# Entry derived from above - image size = 409,600, from Dave Dunfield +diskdef pmc101 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 3 +end + +# BEGIN td143ssdd8 Turbo Dos 1.43 - SSDD 8" - 512 x 16 +# Test OK - image size = 630,784, from Don Maslin's archive +diskdef td143ssdd8 + seclen 512 + tracks 77 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 0 + os 2.2 +# DENSITY MFM ,LOW +end + +# BEGIN headsdd8 Heath H89, Magnolia CP/M - SSDD 8" - 512 x 16 +# Test OK - image size = 630,784, from Don Maslin's archive +diskdef heassdd8 + seclen 512 + tracks 77 + sectrk 16 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +# DENSITY MFM ,LOW +end + +# Morrow Designs Micro-Decision DOUBLE +# 64k CP/M Vers. 2.2 Rev.2.3 SIDED +# Copyright '76, '77, '78, '79, '80 +# Digital Research +# Copyright 1982,1983 Morrow Designs, Inc. +# Assuming 1:1 for Binary output +# 0/0 250 kbps DD 5x1024 +# 80 tracks(40/40), 400 sectors (128 Compressed) +# Entry derived from above data +# Test OK - image siae = 409600, from Dave Dunfield +diskdef mordsdd + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 3 + boottrk 2 + OS 2.2 +end + + +# BEGIN morsddd Morrow MD2 - SSDD 48 tpi 5.25" - 1024 x 5 +# Test OK - image size = 204,800, from Don Maslin's archive +# Also tested with image from Dave Dunfield +diskdef morsddd + seclen 1024 + tracks 40 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 3 + boottrk 2 + os 2.2 +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 94 DRM 127 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN osb1sssd Osborne 1 - SSSD 48 tpi 5.25" - 256 x 10 +# Test OK - image size = 102,400, from Don Maslin's archive +diskdef osb1sssd + seclen 256 + tracks 40 + sectrk 10 + blocksize 2048 + maxdir 64 + skew 2 + boottrk 3 + os 2.2 +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 45 DRM 63 AL0 080H AL1 0 OFS 3 +end + +# BEGIN ampdsdd Ampro - DSDD 48 tpi 5.25" - 512 x 10 +# Test OK - image size = 409,600, from Don Maslin's archive +diskdef ampdsdd + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 + libdsk:format ampro400d +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN ampdsdd80 Ampro - DSDD 96 tpi 5.25" - 512 x 10 +# Test OK - image size = 819,200, from Don Maslin's archive +diskdef ampdsdd80 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 + libdsk:format ampro800 +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN altdsdd Altos - DSDD 5" - 512 x 9 +# Test OK - both CP/M and MP/M - image size = 737,280, from Dave Dunfield +diskdef altdsdd + seclen 512 + tracks 160 + sectrk 9 + blocksize 4096 + maxdir 177 + skew 0 + boottrk 2 + os 3 +# DENSITY MFM ,HIGH +# BSH 5 BLM 31 EXM 3 DSM 176 DRM 176 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN trsomsssd TRS-80 Model 1, Omikron CP/M - SSSD 48 tpi 5.25" - 128 x 18 +# Test OK - image size = 80,640, from TRS-80 Yahoo Group posting +diskdef trsomsssd + seclen 128 + tracks 35 + sectrk 18 + blocksize 1024 + maxdir 64 + skew 4 + boottrk 3 + os 2.2 +# DENSITY FM ,LOW +# BSH 3 BLM 7 EXM 0 DSM 71 DRM 63 AL0 0C0H AL1 0 OFS 3 +end + +# Memotech type 03, ie: 3.5" or 5.25", D/S, D/D, S/T +# 40 tracks, 2 sides, 16 sectors/track, 256 bytes/sector +# Bytes on the media = 2*40*16*256 = 327680 +# CP/M sees 26 128 byte records per track (similar to 8" disks). +# Tracks = 327680/(26*128) = 98 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = ((98-2)*26*128)/2048 = 156, which agrees with DPB + +diskdef memotech-type03 + seclen 128 + tracks 98 + sectrk 26 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 07, ie: 3.5" or 5.25", D/S, D/D, D/T +# 80 tracks, 2 sides, 16 sectors/track, 256 bytes/sector +# Bytes on the media = 2*80*16*256 = 655360 +# CP/M sees 26 128 byte records per track (similar to 8" disks). +# Tracks = 655360/(26*128) = 196 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = ((196-2)*26*128)/2048 = 315, which agrees with DPB + +diskdef memotech-type07 + seclen 128 + tracks 196 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 43, ie: 1MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 1048576/(26*128) = 315.07 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (1048576-2*26*128)/4096 = 254, which agrees with DPB +# Blocks = ((315-2)*26*128)/4096 = 254, so we don't need the 0.07 track +diskdef memotech-type43 + seclen 128 + tracks 315 + sectrk 26 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 47, ie: 2MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 2097152/(26*128) = 630.15 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (2097152-2*26*128)/4096 = 510, which agrees with DPB +# Blocks = ((630-2)*26*128)/4096 = 510, so we don't need the 0.15 track +diskdef memotech-type47 + seclen 128 + tracks 630 + sectrk 26 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 4B, ie: 4MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 4194304/(26*128) = 1260.3 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (4194304-2*26*128)/4096 = 1022, which agrees with DPB +# Blocks = ((1260-2)*26*128)/4096 = 1022, so we don't need the 0.3 track +diskdef memotech-type4B + seclen 128 + tracks 1260 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 4F, ie: 8MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 8388608/(26*128) = 2520.61 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (8388608-2*26*128)/4096 = 2046, which agrees with DPB +# Blocks = ((2520-2)*26*128)/4096 = 2045, so we need the extra 0.61 track +diskdef memotech-type4F + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 18, ie: 8MB SD Card +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for SD Cards includes blocks on the last incomplete track +# Tracks = 8388608/(26*128) = 2520.61 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (8388608-2*26*128)/4096 = 2046, which agrees with DPB +# Blocks = ((2520-2)*26*128)/4096 = 2045, so we need the extra 0.61 track +diskdef memotech-type18 + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 19, ie: 8MB SD Card +diskdef memotech-type19 + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 8M +end + +# Memotech type 1A, ie: 8MB SD Card +diskdef memotech-type1A + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 16M +end + +# Memotech type 1B, ie: 8MB SD Card +diskdef memotech-type1B + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 24M +end + +# Memotech type 1C, ie: 8MB SD Card +diskdef memotech-type1C + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 32M +end + +# Memotech type 1D, ie: 8MB SD Card +diskdef memotech-type1D + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 40M +end + +# Memotech type 1E, ie: 8MB SD Card +diskdef memotech-type1E + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 48M +end + +# Memotech type 1F, ie: 8MB SD Card +diskdef memotech-type1F + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 56M +end + +# Memotech type 50, ie: 256KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 262144/(26*128) = 78.76 +# Data is in 1024 byte blocks, on track 2 onwards +# Blocks = (262144-2*26*128)/1024 = 249, which agrees with DPB +# Blocks = ((78-2)*26*128)/1024 = 247, so we need the extra 0.76 track +diskdef memotech-type50 + seclen 128 + tracks 79 + sectrk 26 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, ie: 512KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 524288/(26*128) = 157.53 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (524288-2*26*128)/2048 = 252, which agrees with DPB +# Blocks = ((157-2)*26*128)/2048 = 251, so we need the extra 0.53 track +diskdef memotech-type51 + seclen 128 + tracks 158 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, as used in Italy, ie: 480KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 491520/(26*128) = 147.69 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (491520-2*26*128)/2048 = 236, which agrees with DPB +# Blocks = ((147-2)*26*128)/2048 = 235, so we need the extra 0.69 track +diskdef memotech-type51-italy + seclen 128 + tracks 148 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, after S2R64.COM, ie: 448KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 458752/(26*128) = 137.84 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (458752-2*26*128)/2048 = 220, which agrees with DPB, after S2R64.COM +# Blocks = ((137-2)*26*128)/2048 = 219, so we need the extra 0.84 track +diskdef memotech-type51-s2r64 + seclen 128 + tracks 138 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, after S2R.COM, ie: 144KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 147456/(26*128) = 44.3 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (147456-2*26*128)/2048 = 68, which agrees with DPB, after S2R.COM +# Blocks = ((44-2)*26*128)/2048 = 68, so we don't need the extra 0.3 track +diskdef memotech-type51-s2r + seclen 128 + tracks 44 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 52, ie: 320KB RAM Disc +# Added for REMEMOTECH +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 327680/(26*128) = 98.46 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (327680-2*26*128)/2048 = 156 +# Blocks = ((98-2)*26*128)/2048 = 156, so we don't need the extra 0.46 track +# This type very deliberately and conveniently exactly matches type 03 +diskdef memotech-type52 + seclen 128 + tracks 98 + sectrk 26 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +# Research Machines 380Z/480Z 5.25" "Single Density" or "MDS" format. +# All tracks are formatted FM 16x128. +diskdef rm-sd + seclen 128 + tracks 40 + sectrk 16 + blocksize 1024 + maxdir 64 + skew 3 + boottrk 3 + os 2.2 +end + +# Research Machines 380Z/480Z 5.25" "Double Density" or "MD" format. +# Track 0 is formatted FM 16x128; 1+ are MFM 9x512. +# If you're working with an image file, make sure that track 0 is +# padded to be the same size as the other tracks. +diskdef rm-dd + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 5 + boottrk 3 + os 2.2 +end + +# Research Machines 380Z/480Z 5.25" "Quad Density" or "MQ" format. +# Track 0 is formatted FM 16x128; 1+ are MFM 9x512. +diskdef rm-qd + seclen 512 + tracks 80 + sectrk 9 + blocksize 2048 + maxdir 128 + skew 5 + boottrk 3 + os 2.2 +end + +# Ampro Little Board Z80 running CP/M 2.21 +# BEGIN AMP1 Ampro - SSDD 48 tpi 5.25" +# DENSITY MFM, LOW +# CYLINDERS 40 SIDES 1 SECTORS 10,512 SKEW 2 +# SIDE1 0 1,2,3,4,5,6,7,8,9,10 +# BSH 4 BLM 15 EXM 1 DSM 94 DRM 63 AL0 080H AL1 0 OFS 2 +# END + +diskdef amp1 + seclen 512 #= Sectors xx,512 + tracks 40 #= (Cylinders * Sides) = 40*1 = 40 + sectrk 10 #= Sectors 10,xxx + blocksize 2048 #= (128*(BLM+1)) = 2048 + maxdir 64 #(DRM+1) = 64 + skew 0 #= SKEW = 0 + boottrk 2 #= OFS = 2 + os 2.2 +end + +#BEGIN AMP2 Ampro - DSDD 48 tpi 5.25" +#DENSITY MFM, LOW +#CYLINDERS 40 SIDES 2 +#SECTORS 10,512 +#SKEW 2 +#SIDE1 0 17,18,19,20,21,22,23,24,25,26 +#SIDE2 1 17,18,19,20,21,22,23,24,25,26 +#ORDER SIDES +#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 DS DD ssize=512 cyl=40 sect=10 head=2 +diskdef amp2 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP3 Ampro - SSDD 96 tpi 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 1 SECTORS 5,1024 SKEW 2 +#SIDE1 0 1,2,3,4,5 +#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 SS DD ssize=1024 cyl=80 sect=5 head=1 +diskdef amp3 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP4 Ampro - DSDD 96 tpi 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 2 SECTORS 5,1024 SKEW 2 +#SIDE1 0 17,18,19,20,21 +#SIDE2 1 17,18,19,20,21 +#ORDER SIDES +#BSH 4 BLM 15 EXM 0 DSM 394 DRM 255 AL0 0F0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 DS DD ssize=1024 cyl=80 sect=5 head=2 +diskdef amp4 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP5 Ampro - SSDD 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 1 SECTORS 5,1024 SKEW 2 +#SIDE1 0 1,2,3,4,5 +#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 SS DD ssize=1024 cyl=80 sect=5 head=1 +diskdef amp5 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP6 Ampro - DSDD 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 2 SECTORS 5,1024 SKEW 2 +#SIDE1 0 17,18,19,20,21 +#SIDE2 1 17,18,19,20,21 +#ORDER SIDES +#BSH 4 BLM 15 EXM 0 DSM 394 DRM 255 AL0 0F0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 DS DD ssize=1024 cyl=80 sect=5 head=2 +diskdef amp6 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef ampro800 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 2 + os 2.2 +end diff --git a/Tools/unix/cpmtools/fsck.cpm.1 b/Tools/unix/cpmtools/fsck.cpm.1 new file mode 100644 index 00000000..a167ef0e --- /dev/null +++ b/Tools/unix/cpmtools/fsck.cpm.1 @@ -0,0 +1,80 @@ +.TH FSCK.CPM 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME ..\"{{{roff}}}\"{{{ +fsck.cpm \- check a CP/M file system +.\"}}} +.SH SYNOPSIS .\"{{{ +.ad l +.B fsck.cpm +.RB [ \-f +.IR format ] +.RB [ \-n ] +.I image +.ad b +.\"}}} +.SH DESCRIPTION .\"{{{ +\fBfsck.cpm\fP is used to check and repair a CP/M file system. After +reading the directory, it makes two passes. The first pass checks extent +fields for range and format violations (bad status, extent number, last +record byte count, file name, extension, block number, record count, +size of \&.COM files, time stamp format, invalid password characters, +invalid time stamp mode). The second pass checks extent connectivity +(multiple allocated blocks and duplicate directory entries). +.P +\fBfsck.cpm\fP can not yet repair all errors. +.\"}}} +.SH OPTIONS .\"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.IP "\fB\-n\fP" +Open the file system read-only and do not repair any errors. +.\"}}} +.SH "RETURN VALUE" .\"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS .\"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH FILES .\"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH DIAGNOSTICS .\"{{{ +.IP "\fIimage\fP: \fIused\fP/\fItotal\fP files (\fIn\fP.\fIn\fP% non-contiguos), \fIused\fP/\fItotal\fP blocks" +No inconsistencies could be found. The number of used files actually +is the number of used extents. Since a file may use more than +one extent, this may be greather than the actual number of files, but a +correct measure would not reflect how many files could still be created +at most. A file is considered fragmented, if sequential data blocks +pointed to by the same extent do not have sequential block numbers. +The number of used blocks includes the blocks used for system tracks +and the directory. +.\"}}} +.SH AUTHORS .\"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" .\"{{{ +.IR fsck (8), +.IR mkfs.cpm (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/fsck.cpm.c b/Tools/unix/cpmtools/fsck.cpm.c new file mode 100644 index 00000000..585015b0 --- /dev/null +++ b/Tools/unix/cpmtools/fsck.cpm.c @@ -0,0 +1,632 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmdir.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* #defines */ /*{{{*/ +/* your favourite password *:-) */ + +#define T0 'G' +#define T1 'E' +#define T2 'H' +#define T3 'E' +#define T4 'I' +#define T5 'M' +#define T6 ' ' +#define T7 ' ' + +#define PB ((char)(T0+T1+T2+T3+T4+T5+T6+T7)) +#define P0 ((char)(T7^PB)) +#define P1 ((char)(T6^PB)) +#define P2 ((char)(T5^PB)) +#define P3 ((char)(T4^PB)) +#define P4 ((char)(T3^PB)) +#define P5 ((char)(T2^PB)) +#define P6 ((char)(T1^PB)) +#define P7 ((char)(T0^PB)) +/*}}}*/ + +/* types */ /*{{{*/ +enum Result { OK=0, MODIFIED=1, BROKEN=2 }; +/*}}}*/ +/* variables */ /*{{{*/ +static int norepair=0; +/*}}}*/ + +/* bcdCheck -- check format and range of BCD digit */ /*{{{*/ +static int bcdCheck(int n, int max, const char *msg, const char *unit, int extent1, int extent2) +{ + if (((n>>4)&0xf)>10 || (n&0xf)>10 || (((n>>4)&0xf)*10+(n&0xf))>=max) + { + printf("Error: Bad %s %s (extent=%d/%d, %s=%02x)\n",msg,unit,extent1,extent2,unit,n&0xff); + return -1; + } + else return 0; +} +/*}}}*/ +/* pwdCheck -- check password */ /*{{{*/ +static int pwdCheck(int extent, const char *pwd, char decode) +{ + char c; + int i; + + for (i=0; i<8; ++i) if ((c=((char)(pwd[7-i]^decode)))<' ' || c&0x80) + { + printf("Error: non-printable character in password (extent=%d, password=",extent); + for (i=0; i<8; ++i) + { + c=pwd[7-i]^decode; + if (c<' ' || c&0x80) + { + putchar('\\'); putchar('0'+((c>>6)&0x01)); + putchar('0'+((c>>3)&0x03)); + putchar('0'+(c&0x03)); + } + else putchar(c); + } + printf(")\n"); + return -1; + } + return 0; +} +/*}}}*/ +/* ask -- ask user and return answer */ /*{{{*/ +static int ask(const char *msg) +{ + while (1) + { + char buf[80]; + + if (norepair) return 0; + printf("%s [Y]? ",msg); fflush(stdout); + if (fgets(buf,sizeof(buf),stdin)==(char*)0) exit(1); + switch (toupper(buf[0])) + { + case '\n': + case 'Y': return 1; + case 'N': return 0; + } + } +} +/*}}}*/ +/* prfile -- print file name */ /*{{{*/ +static char *prfile(struct cpmSuperBlock *sb, int extent) +{ + struct PhysDirectoryEntry *dir; + static char name[80]; + char *s=name; + int i; + char c; + + dir=sb->dir+extent; + for (i=0; i<8; ++i) + { + c=dir->name[i]; + if ((c&0x7f)<' ') + { + *s++='\\'; *s++=('0'+((c>>6)&0x01)); + *s++=('0'+((c>>3)&0x03)); + *s++=('0'+(c&0x03)); + } + else *s++=(c&0x7f); + } + *s++='.'; + for (i=0; i<3; ++i) + { + c=dir->ext[i]; + if ((c&0x7f)<' ') + { + *s++='\\'; *s++=('0'+((c>>6)&0x01)); + *s++=('0'+((c>>3)&0x03)); + *s++=('0'+(c&0x03)); + } + else *s++=(c&0x7f); + } + *s='\0'; + return name; +} +/*}}}*/ +/* fsck -- file system check */ /*{{{*/ +static int fsck(struct cpmInode *root, const char *image) +{ + /* variables */ /*{{{*/ + enum Result ret=OK; + int extent,extent2; + struct PhysDirectoryEntry *dir,*dir2; + struct cpmSuperBlock *sb=root->sb; + /*}}}*/ + + /* Phase 1: check extent fields */ /*{{{*/ + printf("Phase 1: check extent fields\n"); + for (extent=0; extentmaxdir; ++extent) + { + char *status; + int usedBlocks=0; + + dir=sb->dir+extent; + status=&dir->status; + if (*status>=0 && *status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* directory entry */ /*{{{*/ + { + /* check name and extension */ /*{{{*/ + { + int i; + char *c; + + for (i=0; i<8; ++i) + { + c=&(dir->name[i]); + if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + for (i=0; i<3; ++i) + { + c=&(dir->ext[i]); + if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check extent number */ /*{{{*/ + if ((dir->extnol&0xff)>0x1f) + { + printf("Error: Bad lower bits of extent number (extent=%d, name=\"%s\", low bits=%d)\n",extent,prfile(sb,extent),dir->extnol&0xff); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + if ((dir->extnoh&0xff)>0x3f) + { + printf("Error: Bad higher bits of extent number (extent=%d, name=\"%s\", high bits=%d)\n",extent,prfile(sb,extent),dir->extnoh&0xff); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + /*}}}*/ + /* check last record byte count */ /*{{{*/ + if ((dir->lrc&0xff)>128) + { + printf("Error: Bad last record byte count (extent=%d, name=\"%s\", lrc=%d)\n",extent,prfile(sb,extent),dir->lrc&0xff); + if (ask("Clear last record byte count")) + { + dir->lrc=(char)0; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + /*}}}*/ + /* check block number range */ /*{{{*/ + { + int block,min,max,i; + + min=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz; + max=sb->size; + for (i=0; i<16; ++i) + { + block=dir->pointers[i]&0xff; + if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8; + if (block>0) + { + ++usedBlocks; + if (block=max) + { + printf("Error: Bad block number (extent=%d, name=\"%s\", block=%d)\n",extent,prfile(sb,extent),block); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check number of used blocks ? */ /*{{{*/ + /*}}}*/ + /* check record count */ /*{{{*/ + { + int i,min,max,recordsInBlocks,used=0; + + min=(dir->extnol%sb->extents)*16/sb->extents; + max=((dir->extnol%sb->extents)+1)*16/sb->extents; + assert(minpointers[i] || (sb->size>=256 && dir->pointers[i+1])) ++used; + if (sb->size >= 256) ++i; + } + recordsInBlocks=(((unsigned char)dir->blkcnt)*128+sb->blksiz-1)/sb->blksiz; + if (recordsInBlocks!=used) + { + printf("Error: Bad record count (extent=%d, name=\"%s\", record count=%d)\n",extent,prfile(sb,extent),dir->blkcnt&0xff); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check for too large .com files */ /*{{{*/ + if (((EXTENT(dir->extnol,dir->extnoh)==3 && dir->blkcnt>=126) || EXTENT(dir->extnol,dir->extnoh)>=4) && (dir->ext[0]&0x7f)=='C' && (dir->ext[1]&0x7f)=='O' && (dir->ext[2]&0x7f)=='M') + { + printf("Warning: Oversized .COM file (extent=%d, name=\"%s\")\n",extent,prfile(sb,extent)); + } + /*}}}*/ + } + /*}}}*/ + else if ((sb->type==CPMFS_P2DOS || sb->type==CPMFS_DR3) && *status==33) /* check time stamps ? */ /*{{{*/ + { + unsigned long created,modified; + char s; + + if ((s=sb->dir[extent2=(extent&~3)].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for first of the three extents */ /*{{{*/ + { + bcdCheck(dir->name[2],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2); + bcdCheck(dir->name[3],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2); + bcdCheck(dir->name[6],24,"modification date","hour",extent,extent2); + bcdCheck(dir->name[7],60,"modification date","minute",extent,extent2); + created=(dir->name[4]+(dir->name[1]<<8))*(0x60*0x60)+dir->name[2]*0x60+dir->name[3]; + modified=(dir->name[0]+(dir->name[5]<<8))*(0x60*0x60)+dir->name[6]*0x60+dir->name[7]; + if (sb->cnotatime && modifieddir[extent2=(extent&~3)+1].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for second */ /*{{{*/ + { + bcdCheck(dir->lrc,24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2); + bcdCheck(dir->extnoh,60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2); + bcdCheck(dir->pointers[1],24,"modification date","hour",extent,extent2); + bcdCheck(dir->pointers[2],60,"modification date","minute",extent,extent2); + created=(dir->ext[2]+(dir->extnol<<8))*(0x60*0x60)+dir->lrc*0x60+dir->extnoh; + modified=(dir->blkcnt+(dir->pointers[0]<<8))*(0x60*0x60)+dir->pointers[1]*0x60+dir->pointers[2]; + if (sb->cnotatime && modifieddir[extent2=(extent&~3)+2].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for third */ /*{{{*/ + { + bcdCheck(dir->pointers[7],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2); + bcdCheck(dir->pointers[8],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2); + bcdCheck(dir->pointers[11],24,"modification date","hour",extent,extent2); + bcdCheck(dir->pointers[12],60,"modification date","minute",extent,extent2); + created=(dir->pointers[5]+(dir->pointers[6]<<8))*(0x60*0x60)+dir->pointers[7]*0x60+dir->pointers[8]; + modified=(dir->pointers[9]+(dir->pointers[10]<<8))*(0x60*0x60)+dir->pointers[11]*0x60+dir->pointers[12]; + if (sb->cnotatime && modifiedtype==CPMFS_DR3 && *status==32) /* disc label */ /*{{{*/ + { + unsigned long created,modified; + + bcdCheck(dir->pointers[10],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent); + bcdCheck(dir->pointers[11],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent); + bcdCheck(dir->pointers[14],24,"modification date","hour",extent,extent); + bcdCheck(dir->pointers[15],60,"modification date","minute",extent,extent); + created=(dir->pointers[8]+(dir->pointers[9]<<8))*(0x60*0x60)+dir->pointers[10]*0x60+dir->pointers[11]; + modified=(dir->pointers[12]+(dir->pointers[13]<<8))*(0x60*0x60)+dir->pointers[14]*0x60+dir->pointers[15]; + if (sb->cnotatime && modifiedextnol&0x40 && dir->extnol&0x10) + { + printf("Error: Bit 4 and 6 can only be exclusively be set (extent=%d, label byte=0x%02x)\n",extent,(unsigned char)dir->extnol); + if (ask("Time stamp on creation")) + { + dir->extnol&=~0x40; + ret|=MODIFIED; + } + else if (ask("Time stamp on access")) + { + dir->extnol&=~0x10; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (dir->extnol&0x80 && pwdCheck(extent,dir->pointers,dir->lrc)) + { + char msg[80]; + + sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7); + if (ask(msg)) + { + dir->pointers[0]=P0; + dir->pointers[1]=P1; + dir->pointers[2]=P2; + dir->pointers[3]=P3; + dir->pointers[4]=P4; + dir->pointers[5]=P5; + dir->pointers[6]=P6; + dir->pointers[7]=P7; + dir->lrc=PB; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + } + /*}}}*/ + else if (sb->type==CPMFS_DR3 && *status>=16 && *status<=31) /* password */ /*{{{*/ + { + /* check name and extension */ /*{{{*/ + { + int i; + char *c; + + for (i=0; i<8; ++i) + { + c=&(dir->name[i]); + if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Clear password entry")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + for (i=0; i<3; ++i) + { + c=&(dir->ext[i]); + if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Clear password entry")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check password */ /*{{{*/ + if (dir->extnol&(0x80|0x40|0x20) && pwdCheck(extent,dir->pointers,dir->lrc)) + { + char msg[80]; + + sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7); + if (ask(msg)) + { + dir->pointers[0]=P0; + dir->pointers[1]=P1; + dir->pointers[2]=P2; + dir->pointers[3]=P3; + dir->pointers[4]=P4; + dir->pointers[5]=P5; + dir->pointers[6]=P6; + dir->pointers[7]=P7; + dir->lrc=PB; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + /*}}}*/ + } + /*}}}*/ + else if (*status!=(char)0xe5) /* bad status */ /*{{{*/ + { + printf("Error: Bad status (extent=%d, name=\"%s\", status=0x%02x)\n",extent,prfile(sb,extent),*status&0xff); + if (ask("Clear entry")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + continue; + } + /*}}}*/ + } + /*}}}*/ + /* Phase 2: check extent connectivity */ /*{{{*/ + printf("Phase 2: check extent connectivity\n"); + /* check multiple allocated blocks */ /*{{{*/ + for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + int i,j,block,block2; + + for (i=0; i<16; ++i) + { + block=dir->pointers[i]&0xff; + if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8; + for (extent2=0; extent2maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + for (j=0; j<16; ++j) + { + block2=dir2->pointers[j]&0xff; + if (sb->size>=256) block2+=(dir2->pointers[++j]&0xff)<<8; + if (block!=0 && block2!=0 && block==block2 && !(extent==extent2 && i==j)) + { + printf("Error: Multiple allocated block (extent=%d,%d, name=\"%s\"",extent,extent2,prfile(sb,extent)); + printf(",\"%s\" block=%d)\n",prfile(sb,extent2),block); + ret|=BROKEN; + } + } + } + } + } + /*}}}*/ + /* check multiple extents */ /*{{{*/ + for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + for (extent2=0; extent2maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + if (extent!=extent2 && EXTENT(dir->extnol,dir->extnoh)==EXTENT(dir2->extnol,dir2->extnoh) && dir->status==dir2->status) + { + int i; + + for (i=0; i<8 && (dir->name[i]&0x7f)==(dir2->name[i]&0x7f); ++i); + if (i==8) + { + for (i=0; i<3 && (dir->ext[i]&0x7f)==(dir2->ext[i]&0x7f); ++i); + if (i==3) + { + printf("Error: Duplicate extent (extent=%d,%d)\n",extent,extent2); + ret|=BROKEN; + } + } + } + } + } + /*}}}*/ + /*}}}*/ + if (ret==0) /* print statistics */ /*{{{*/ + { + struct cpmStatFS statfsbuf; + int fragmented=0,borders=0; + + cpmStatFS(root,&statfsbuf); + for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + int i,block,previous=-1; + + for (i=0; i<16; ++i) + { + block=dir->pointers[i]&0xff; + if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8; + if (previous!=-1) + { + if (block!=0 && block!=(previous+1)) ++fragmented; + ++borders; + } + previous=block; + } + } + fragmented=(borders ? (1000*fragmented)/borders : 0); + printf("%s: %ld/%ld files (%d.%d%% non-contigous), %ld/%ld blocks\n",image,statfsbuf.f_files-statfsbuf.f_ffree,statfsbuf.f_files,fragmented/10,fragmented%10,statfsbuf.f_blocks-statfsbuf.f_bfree,statfsbuf.f_blocks); + } + /*}}}*/ + return ret; +} +/*}}}*/ + +const char cmd[]="fsck.cpm"; + +/* main */ /*{{{*/ +int main(int argc, char *argv[]) +{ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,usage=0; + struct cpmSuperBlock sb; + struct cpmInode root; + enum Result ret; + + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:nh?"))!=EOF) switch(c) + { + case 'f': format=optarg; break; + case 'T': devopts=optarg; break; + case 'n': norepair=1; break; + case 'h': + case '?': usage=1; break; + } + + if (optind!=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-n] image\n",cmd); + exit(1); + } + if ((err=Device_open(&sb.dev, image, (norepair ? O_RDONLY : O_RDWR), devopts))) + { + if ((err=Device_open(&sb.dev, image,O_RDONLY, devopts))) + { + fprintf(stderr,"%s: cannot open %s: %s\n",cmd,image,err); + exit(1); + } + else + { + fprintf(stderr,"%s: cannot open %s for writing, no repair possible\n",cmd,image); + } + } + if (cpmReadSuper(&sb,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + ret=fsck(&root,image); + if (ret&MODIFIED) + { + if (cpmSync(&sb)==-1) + { + fprintf(stderr,"%s: write error on %s: %s\n",cmd,image,strerror(errno)); + ret|=BROKEN; + } + fprintf(stderr,"%s: FILE SYSTEM ON %s MODIFIED",cmd,image); + if (ret&BROKEN) fprintf(stderr,", PLEASE CHECK AGAIN"); + fprintf(stderr,"\n"); + } + cpmUmount(&sb); + if (ret&BROKEN) return 2; + else return 0; +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/fsed.cpm.1 b/Tools/unix/cpmtools/fsed.cpm.1 new file mode 100644 index 00000000..a6d58738 --- /dev/null +++ b/Tools/unix/cpmtools/fsed.cpm.1 @@ -0,0 +1,62 @@ +.TH FSED.CPM 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME ..\"{{{roff}}}\"{{{ +fsed.cpm \- edit a CP/M file system +.\"}}} +.SH SYNOPSIS .\"{{{ +.ad l +.B fsed.cpm +.RB [ \-f +.IR format ] +.I image +.ad b +.\"}}} +.SH DESCRIPTION .\"{{{ +\fBfsed.cpm\fP edits a CP/M file system on an image file or device. +It knows about the system, directory and data area, using sector skew on +the last two. Directory entries are decoded. The interactive usage is +self-explanatory. +.\"}}} +.SH OPTIONS .\"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.\"}}} +.SH "RETURN VALUE" .\"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS .\"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES .\"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" .\"{{{ +.IR fsck.cpm (1), +.IR mkfs.cpm (1), +.IR cpmls (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/fsed.cpm.c b/Tools/unix/cpmtools/fsed.cpm.c new file mode 100644 index 00000000..f120b77c --- /dev/null +++ b/Tools/unix/cpmtools/fsed.cpm.c @@ -0,0 +1,748 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#if NEED_NCURSES +#if HAVE_NCURSES_NCURSES_H +#include +#else +#include +#endif +#else +#include +#endif +#include +#include +#include +#include +#include + +#include "cpmfs.h" +#include "getopt_.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +extern char **environ; + +static char *mapbuf; + +static struct tm *cpmtime(char lday, char hday, char hour, char min) /*{{{*/ +{ + static struct tm tm; + unsigned long days=(lday&0xff)|((hday&0xff)<<8); + int d; + unsigned int md[12]={31,0,31,30,31,30,31,31,30,31,30,31}; + + tm.tm_sec=0; + tm.tm_min=((min>>4)&0xf)*10+(min&0xf); + tm.tm_hour=((hour>>4)&0xf)*10+(hour&0xf); + tm.tm_mon=0; + tm.tm_year=1978; + tm.tm_isdst=-1; + if (days) --days; + while (days>=(d=(((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 366 : 365))) + { + days-=d; + ++tm.tm_year; + } + md[1]=((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 29 : 28; + while (days>=md[tm.tm_mon]) + { + days-=md[tm.tm_mon]; + ++tm.tm_mon; + } + tm.tm_mday=days+1; + tm.tm_year-=1900; + return &tm; +} +/*}}}*/ +static void info(struct cpmSuperBlock *sb, const char *format, const char *image) /*{{{*/ +{ + const char *msg; + + clear(); + msg="File system characteristics"; + move(0,(COLS-strlen(msg))/2); printw(msg); + move(2,0); printw(" Image: %s",image); + move(3,0); printw(" Format: %s",format); + move(4,0); printw(" File system: "); + switch (sb->type) + { + case CPMFS_DR22: printw("CP/M 2.2"); break; + case CPMFS_P2DOS: printw("P2DOS 2.3"); break; + case CPMFS_DR3: printw("CP/M Plus"); break; + } + + move(6,0); printw(" Sector length: %d",sb->secLength); + move(7,0); printw(" Number of tracks: %d",sb->tracks); + move(8,0); printw(" Sectors per track: %d",sb->sectrk); + + move(10,0);printw(" Block size: %d",sb->blksiz); + move(11,0);printw("Number of directory entries: %d",sb->maxdir); + move(12,0);printw(" Logical sector skew: %d",sb->skew); + move(13,0);printw(" Number of system tracks: %d",sb->boottrk); + move(14,0);printw(" Logical extents per extent: %d",sb->extents); + move(15,0);printw(" Allocatable data blocks: %d",sb->size-(sb->maxdir*32+sb->blksiz-1)/sb->blksiz); + + msg="Any key to continue"; + move(23,(COLS-strlen(msg))/2); printw(msg); + getch(); +} +/*}}}*/ +static void map(struct cpmSuperBlock *sb) /*{{{*/ +{ + const char *msg; + char bmap[18*80]; + int secmap,sys,directory; + int pos; + + clear(); + msg="Data map"; + move(0,(COLS-strlen(msg))/2); printw(msg); + + secmap=(sb->tracks*sb->sectrk+80*18-1)/(80*18); + memset(bmap,' ',sizeof(bmap)); + sys=sb->boottrk*sb->sectrk; + memset(bmap,'S',sys/secmap); + directory=(sb->maxdir*32+sb->secLength-1)/sb->secLength; + memset(bmap+sys/secmap,'D',directory/secmap); + memset(bmap+(sys+directory)/secmap,'.',sb->sectrk*sb->tracks/secmap); + + for (pos=0; pos<(sb->maxdir*32+sb->secLength-1)/sb->secLength; ++pos) + { + int entry; + + Device_readSector(&sb->dev,sb->boottrk+pos/(sb->sectrk*sb->secLength),pos/sb->secLength,mapbuf); + for (entry=0; entrysecLength/32 && (pos*sb->secLength/32)+entrymaxdir; ++entry) + { + int i; + + if (mapbuf[entry*32]>=0 && mapbuf[entry*32]<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + for (i=0; i<16; ++i) + { + int sector; + + sector=mapbuf[entry*32+16+i]&0xff; + if (sb->size>=256) sector|=(((mapbuf[entry*32+16+ ++i]&0xff)<<8)); + if (sector>0 && sector<=sb->size) + { + /* not entirely correct without the last extent record count */ + sector=sector*(sb->blksiz/sb->secLength)+sb->sectrk*sb->boottrk; + memset(bmap+sector/secmap,'#',sb->blksiz/(sb->secLength*secmap)); + } + } + } + } + } + + for (pos=0; pos<(int)sizeof(bmap); ++pos) + { + move(2+pos%18,pos/18); + addch(bmap[pos]); + } + move(21,0); printw("S=System area D=Directory area #=File data .=Free"); + msg="Any key to continue"; + move(23,(COLS-strlen(msg))/2); printw(msg); + getch(); +} +/*}}}*/ +static void data(struct cpmSuperBlock *sb, const char *buf, unsigned long int pos) /*{{{*/ +{ + int offset=(pos%sb->secLength)&~0x7f; + unsigned int i; + + for (i=0; i<128; ++i) + { + move(4+(i>>4),(i&0x0f)*3+!!(i&0x8)); printw("%02x",buf[i+offset]&0xff); + if (pos%sb->secLength==i+offset) attron(A_REVERSE); + move(4+(i>>4),50+(i&0x0f)); printw("%c",isprint(buf[i+offset]) ? buf[i+offset] : '.'); + attroff(A_REVERSE); + } + move(4+((pos&0x7f)>>4),((pos&0x7f)&0x0f)*3+!!((pos&0x7f)&0x8)+1); +} +/*}}}*/ + +const char cmd[]="fsed.cpm"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *devopts=(const char*)0; + char *image; + const char *err; + struct cpmSuperBlock drive; + struct cpmInode root; + const char *format; + int c,usage=0; + off_t pos; + chtype ch; + int reload; + char *buf; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'f': format=optarg; break; + case 'T': devopts=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind!=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: fsed.cpm [-f format] image\n"); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev,image,O_RDONLY,devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + /* alloc sector buffers */ /*{{{*/ + if ((buf=malloc(drive.secLength))==(char*)0 || (mapbuf=malloc(drive.secLength))==(char*)0) + { + fprintf(stderr,"fsed.cpm: can not allocate sector buffer (%s).\n",strerror(errno)); + exit(1); + } + /*}}}*/ + /* init curses */ /*{{{*/ + initscr(); + noecho(); + raw(); + nonl(); + idlok(stdscr,TRUE); + idcok(stdscr,TRUE); + keypad(stdscr,TRUE); + clear(); + /*}}}*/ + + pos=0; + reload=1; + do + { + /* display position and load data */ /*{{{*/ + clear(); + move(2,0); printw("Byte %8lu (0x%08lx) ",pos,pos); + if (pos<(drive.boottrk*drive.sectrk*drive.secLength)) + { + printw("Physical sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1); + } + else + { + printw("Sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1); + printw("(physical %3d) ",drive.skewtab[(pos/drive.secLength)%drive.sectrk]+1); + } + printw("Offset %5lu ",pos%drive.secLength); + printw("Track %5lu",pos/(drive.secLength*drive.sectrk)); + move(LINES-3,0); printw("N)ext track P)revious track"); + move(LINES-2,0); printw("n)ext record p)revious record f)orward byte b)ackward byte"); + move(LINES-1,0); printw("i)nfo q)uit"); + if (reload) + { + if (pos<(drive.boottrk*drive.sectrk*drive.secLength)) + { + err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),(pos/drive.secLength)%drive.sectrk,buf); + } + else + { + err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),drive.skewtab[(pos/drive.secLength)%drive.sectrk],buf); + } + if (err) + { + move(4,0); printw("Data can not be read: %s",err); + } + else reload=0; + } + /*}}}*/ + + if /* position before end of system area */ /*{{{*/ + (pos<(drive.boottrk*drive.sectrk*drive.secLength)) + { + const char *msg; + + msg="System area"; move(0,(COLS-strlen(msg))/2); printw(msg); + move(LINES-3,36); printw("F)orward 16 byte B)ackward 16 byte"); + if (!reload) data(&drive,buf,pos); + switch (ch=getch()) + { + case 'F': /* next 16 byte */ /*{{{*/ + { + if (pos+16<(drive.sectrk*drive.tracks*(off_t)drive.secLength)) + { + if (pos/drive.secLength!=(pos+16)/drive.secLength) reload=1; + pos+=16; + } + break; + } + /*}}}*/ + case 'B': /* previous 16 byte */ /*{{{*/ + { + if (pos>=16) + { + if (pos/drive.secLength!=(pos-16)/drive.secLength) reload=1; + pos-=16; + } + break; + } + /*}}}*/ + } + } + /*}}}*/ + else if /* position before end of directory area */ /*{{{*/ + (pos<(drive.boottrk*drive.sectrk*drive.secLength+drive.maxdir*32)) + { + const char *msg; + unsigned long entrystart=(pos&~0x1f)%drive.secLength; + int entry=(pos-(drive.boottrk*drive.sectrk*drive.secLength))>>5; + int offset=pos&0x1f; + + msg="Directory area"; move(0,(COLS-strlen(msg))/2); printw(msg); + move(LINES-3,36); printw("F)orward entry B)ackward entry"); + + move(13,0); printw("Entry %3d: ",entry); + if /* free or used directory entry */ /*{{{*/ + ((buf[entrystart]>=0 && buf[entrystart]<=(drive.type==CPMFS_P2DOS ? 31 : 15)) || buf[entrystart]==(char)0xe5) + { + int i; + + if (buf[entrystart]==(char)0xe5) + { + if (offset==0) attron(A_REVERSE); + printw("Free"); + attroff(A_REVERSE); + } + else printw("Directory entry"); + move(15,0); + if (buf[entrystart]!=(char)0xe5) + { + printw("User: "); + if (offset==0) attron(A_REVERSE); + printw("%2d",buf[entrystart]); + attroff(A_REVERSE); + printw(" "); + } + printw("Name: "); + for (i=0; i<8; ++i) + { + if (offset==1+i) attron(A_REVERSE); + printw("%c",buf[entrystart+1+i]&0x7f); + attroff(A_REVERSE); + } + printw(" Extension: "); + for (i=0; i<3; ++i) + { + if (offset==9+i) attron(A_REVERSE); + printw("%c",buf[entrystart+9+i]&0x7f); + attroff(A_REVERSE); + } + move(16,0); printw("Extent: %3d",((buf[entrystart+12]&0xff)+((buf[entrystart+14]&0xff)<<5))/drive.extents); + printw(" (low: "); + if (offset==12) attron(A_REVERSE); + printw("%2d",buf[entrystart+12]&0xff); + attroff(A_REVERSE); + printw(", high: "); + if (offset==14) attron(A_REVERSE); + printw("%2d",buf[entrystart+14]&0xff); + attroff(A_REVERSE); + printw(")"); + move(17,0); printw("Last extent record count: "); + if (offset==15) attron(A_REVERSE); + printw("%3d",buf[entrystart+15]&0xff); + attroff(A_REVERSE); + move(18,0); printw("Last record byte count: "); + if (offset==13) attron(A_REVERSE); + printw("%3d",buf[entrystart+13]&0xff); + attroff(A_REVERSE); + move(19,0); printw("Data blocks:"); + for (i=0; i<16; ++i) + { + unsigned int block=buf[entrystart+16+i]&0xff; + if (drive.size>=256) + { + printw(" "); + if (offset==16+i || offset==16+i+1) attron(A_REVERSE); + printw("%5d",block|(((buf[entrystart+16+ ++i]&0xff)<<8))); + attroff(A_REVERSE); + } + else + { + printw(" "); + if (offset==16+i) attron(A_REVERSE); + printw("%3d",block); + attroff(A_REVERSE); + } + } + } + /*}}}*/ + else if /* disc label */ /*{{{*/ + (buf[entrystart]==0x20 && drive.type==CPMFS_DR3) + { + int i; + const struct tm *tm; + char s[30]; + + if (offset==0) attron(A_REVERSE); + printw("Disc label"); + attroff(A_REVERSE); + move(15,0); + printw("Label: "); + for (i=0; i<11; ++i) + { + if (i+1==offset) attron(A_REVERSE); + printw("%c",buf[entrystart+1+i]&0x7f); + attroff(A_REVERSE); + } + move(16,0); + printw("Bit 0,7: "); + if (offset==12) attron(A_REVERSE); + printw("Label %s",buf[entrystart+12]&1 ? "set" : "not set"); + printw(", password protection %s",buf[entrystart+12]&0x80 ? "set" : "not set"); + attroff(A_REVERSE); + move(17,0); + printw("Bit 4,5,6: "); + if (offset==12) attron(A_REVERSE); + printw("Time stamp "); + if (buf[entrystart+12]&0x10) printw("on create, "); + else printw("not on create, "); + if (buf[entrystart+12]&0x20) printw("on modification, "); + else printw("not on modifiction, "); + if (buf[entrystart+12]&0x40) printw("on access"); + else printw("not on access"); + attroff(A_REVERSE); + move(18,0); + printw("Password: "); + for (i=0; i<8; ++i) + { + char printable; + + if (offset==16+(7-i)) attron(A_REVERSE); + printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f; + printw("%c",isprint(printable) ? printable : ' '); + attroff(A_REVERSE); + } + printw(" XOR value: "); + if (offset==13) attron(A_REVERSE); + printw("0x%02x",buf[entrystart+13]&0xff); + attroff(A_REVERSE); + move(19,0); + printw("Created: "); + tm=cpmtime(buf[entrystart+24],buf[entrystart+25],buf[entrystart+26],buf[entrystart+27]); + if (offset==24 || offset==25) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==26) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==27) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Updated: "); + tm=cpmtime(buf[entrystart+28],buf[entrystart+29],buf[entrystart+30],buf[entrystart+31]); + if (offset==28 || offset==29) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==30) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==31) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + } + /*}}}*/ + else if /* time stamp */ /*{{{*/ + (buf[entrystart]==0x21 && (drive.type==CPMFS_P2DOS || drive.type==CPMFS_DR3)) + { + const struct tm *tm; + char s[30]; + + if (offset==0) attron(A_REVERSE); + printw("Time stamps"); + attroff(A_REVERSE); + move(15,0); + printw("3rd last extent: Created/Accessed "); + tm=cpmtime(buf[entrystart+1],buf[entrystart+2],buf[entrystart+3],buf[entrystart+4]); + if (offset==1 || offset==2) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==3) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==4) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Modified "); + tm=cpmtime(buf[entrystart+5],buf[entrystart+6],buf[entrystart+7],buf[entrystart+8]); + if (offset==5 || offset==6) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==7) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==8) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + + move(16,0); + printw("2nd last extent: Created/Accessed "); + tm=cpmtime(buf[entrystart+11],buf[entrystart+12],buf[entrystart+13],buf[entrystart+14]); + if (offset==11 || offset==12) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==13) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==14) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Modified "); + tm=cpmtime(buf[entrystart+15],buf[entrystart+16],buf[entrystart+17],buf[entrystart+18]); + if (offset==15 || offset==16) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==17) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==18) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + + move(17,0); + printw(" Last extent: Created/Accessed "); + tm=cpmtime(buf[entrystart+21],buf[entrystart+22],buf[entrystart+23],buf[entrystart+24]); + if (offset==21 || offset==22) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==23) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==24) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Modified "); + tm=cpmtime(buf[entrystart+25],buf[entrystart+26],buf[entrystart+27],buf[entrystart+28]); + if (offset==25 || offset==26) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==27) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==28) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + } + /*}}}*/ + else if /* password */ /*{{{*/ + (buf[entrystart]>=16 && buf[entrystart]<=31 && drive.type==CPMFS_DR3) + { + int i; + + if (offset==0) attron(A_REVERSE); + printw("Password"); + attroff(A_REVERSE); + + move(15,0); + printw("Name: "); + for (i=0; i<8; ++i) + { + if (offset==1+i) attron(A_REVERSE); + printw("%c",buf[entrystart+1+i]&0x7f); + attroff(A_REVERSE); + } + printw(" Extension: "); + for (i=0; i<3; ++i) + { + if (offset==9+i) attron(A_REVERSE); + printw("%c",buf[entrystart+9+i]&0x7f); + attroff(A_REVERSE); + } + + move(16,0); + printw("Password required for: "); + if (offset==12) attron(A_REVERSE); + if (buf[entrystart+12]&0x80) printw("Reading "); + if (buf[entrystart+12]&0x40) printw("Writing "); + if (buf[entrystart+12]&0x20) printw("Deleting "); + attroff(A_REVERSE); + + move(17,0); + printw("Password: "); + for (i=0; i<8; ++i) + { + char printable; + + if (offset==16+(7-i)) attron(A_REVERSE); + printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f; + printw("%c",isprint(printable) ? printable : ' '); + attroff(A_REVERSE); + } + printw(" XOR value: "); + if (offset==13) attron(A_REVERSE); + printw("0x%02x",buf[entrystart+13]&0xff); + attroff(A_REVERSE); + } + /*}}}*/ + else /* bad status */ /*{{{*/ + { + printw("Bad status "); + if (offset==0) attron(A_REVERSE); + printw("0x%02x",buf[entrystart]); + attroff(A_REVERSE); + } + /*}}}*/ + if (!reload) data(&drive,buf,pos); + switch (ch=getch()) + { + case 'F': /* next entry */ /*{{{*/ + { + if (pos+32<(drive.sectrk*drive.tracks*(off_t)drive.secLength)) + { + if (pos/drive.secLength!=(pos+32)/drive.secLength) reload=1; + pos+=32; + } + break; + } + /*}}}*/ + case 'B': /* previous entry */ /*{{{*/ + { + if (pos>=32) + { + if (pos/drive.secLength!=(pos-32)/drive.secLength) reload=1; + pos-=32; + } + break; + } + /*}}}*/ + } + } + /*}}}*/ + else /* data area */ /*{{{*/ + { + const char *msg; + + msg="Data area"; move(0,(COLS-strlen(msg))/2); printw(msg); + if (!reload) data(&drive,buf,pos); + ch=getch(); + } + /*}}}*/ + + /* process common commands */ /*{{{*/ + switch (ch) + { + case 'n': /* next record */ /*{{{*/ + { + if (pos+128<(drive.sectrk*drive.tracks*(off_t)drive.secLength)) + { + if (pos/drive.secLength!=(pos+128)/drive.secLength) reload=1; + pos+=128; + } + break; + } + /*}}}*/ + case 'p': /* previous record */ /*{{{*/ + { + if (pos>=128) + { + if (pos/drive.secLength!=(pos-128)/drive.secLength) reload=1; + pos-=128; + } + break; + } + /*}}}*/ + case 'N': /* next track */ /*{{{*/ + { + if ((pos+drive.sectrk*drive.secLength)<(drive.sectrk*drive.tracks*drive.secLength)) + { + pos+=drive.sectrk*drive.secLength; + reload=1; + } + break; + } + /*}}}*/ + case 'P': /* previous track */ /*{{{*/ + { + if (pos>=drive.sectrk*drive.secLength) + { + pos-=drive.sectrk*drive.secLength; + reload=1; + } + break; + } + /*}}}*/ + case 'b': /* byte back */ /*{{{*/ + { + if (pos) + { + if (pos/drive.secLength!=(pos-1)/drive.secLength) reload=1; + --pos; + } + break; + } + /*}}}*/ + case 'f': /* byte forward */ /*{{{*/ + { + if (pos+1 +#include +#include +#include + +#ifdef __VMS +# include +#endif + +#ifdef _LIBC +# include +#else +#if 0 +# include +# define _(msgid) gettext (msgid) +#else +# define _(msgid) (msgid) +#endif +#endif + +#if defined _LIBC && defined USE_IN_LIBIO +# include +#endif + +#ifndef attribute_hidden +# define attribute_hidden +#endif + +/* Unlike standard Unix `getopt', functions like `getopt_long' + let the user intersperse the options with the other arguments. + + As `getopt_long' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Using `getopt' or setting the environment variable POSIXLY_CORRECT + disables permutation. + Then the application's behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt_int.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Keep a global copy of all internal members of getopt_data. */ + +static struct _getopt_data getopt_data; + + +#if defined HAVE_DECL_GETENV && !HAVE_DECL_GETENV +extern char *getenv (); +#endif + +#ifdef _LIBC +/* Stored original parameters. + XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ +extern int __libc_argc; +extern char **__libc_argv; + +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +# ifdef USE_NONOPTION_FLAGS +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; +# endif + +# ifdef USE_NONOPTION_FLAGS +# define SWAP_FLAGS(ch1, ch2) \ + if (d->__nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +# else +# define SWAP_FLAGS(ch1, ch2) +# endif +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (char **argv, struct _getopt_data *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (d->__nonoption_flags_len > 0 && top >= d->__nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = malloc (top + 1); + if (new_str == NULL) + d->__nonoption_flags_len = d->__nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + d->__nonoption_flags_max_len), + '\0', top + 1 - d->__nonoption_flags_max_len); + d->__nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char * +_getopt_initialize (int argc, char **argv, const char *optstring, + int posixly_correct, struct _getopt_data *d) +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + d->__first_nonopt = d->__last_nonopt = d->optind; + + d->__nextchar = NULL; + + d->__posixly_correct = posixly_correct || !!getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + if (!d->__posixly_correct + && argc == __libc_argc && argv == __libc_argv) + { + if (d->__nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + d->__nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = d->__nonoption_flags_max_len = strlen (orig_str); + if (d->__nonoption_flags_max_len < argc) + d->__nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) malloc (d->__nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + d->__nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', d->__nonoption_flags_max_len - len); + } + } + d->__nonoption_flags_len = d->__nonoption_flags_max_len; + } + else + d->__nonoption_flags_len = 0; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. + + If POSIXLY_CORRECT is nonzero, behave as if the POSIXLY_CORRECT + environment variable were set. */ + +int +_getopt_internal_r (int argc, char **argv, const char *optstring, + const struct option *longopts, int *longind, + int long_only, int posixly_correct, struct _getopt_data *d) +{ + int print_errors = d->opterr; + if (optstring[0] == ':') + print_errors = 0; + + if (argc < 1) + return -1; + + d->optarg = NULL; + + if (d->optind == 0 || !d->__initialized) + { + if (d->optind == 0) + d->optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring, + posixly_correct, d); + d->__initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#if defined _LIBC && defined USE_NONOPTION_FLAGS +# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0' \ + || (d->optind < d->__nonoption_flags_len \ + && __getopt_nonoption_flags[d->optind] == '1')) +#else +# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0') +#endif + + if (d->__nextchar == NULL || *d->__nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + + if (d->__ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (d->__first_nonopt != d->__last_nonopt + && d->__last_nonopt != d->optind) + exchange ((char **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (d->optind < argc && NONOPTION_P) + d->optind++; + d->__last_nonopt = d->optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (d->optind != argc && !strcmp (argv[d->optind], "--")) + { + d->optind++; + + if (d->__first_nonopt != d->__last_nonopt + && d->__last_nonopt != d->optind) + exchange ((char **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + + d->optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (d->optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + d->__nextchar = (argv[d->optind] + 1 + + (longopts != NULL && argv[d->optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[d->optind][1] == '-' + || (long_only && (argv[d->optind][2] + || !strchr (optstring, argv[d->optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else if (long_only + || pfound->has_arg != p->has_arg + || pfound->flag != p->flag + || pfound->val != p->val) + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[d->optind]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[d->optind]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + d->optind++; + d->optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + d->optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + int n; +#endif + + if (argv[d->optind - 1][1] == '-') + { + /* --option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("\ +%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); +#else + fprintf (stderr, _("\ +%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); +#endif + } + else + { + /* +option or -option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("\ +%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[d->optind - 1][0], + pfound->name); +#else + fprintf (stderr, _("\ +%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[d->optind - 1][0], + pfound->name); +#endif + } + +#if defined _LIBC && defined USE_IN_LIBIO + if (n >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#endif + } + + d->__nextchar += strlen (d->__nextchar); + + d->optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + d->optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen (d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[d->optind][1] == '-' + || strchr (optstring, *d->__nextchar) == NULL) + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + int n; +#endif + + if (argv[d->optind][1] == '-') + { + /* --option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: unrecognized option `--%s'\n"), + argv[0], d->__nextchar); +#else + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], d->__nextchar); +#endif + } + else + { + /* +option or -option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[d->optind][0], d->__nextchar); +#else + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[d->optind][0], d->__nextchar); +#endif + } + +#if defined _LIBC && defined USE_IN_LIBIO + if (n >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#endif + } + d->__nextchar = (char *) ""; + d->optind++; + d->optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *d->__nextchar++; + char *temp = strchr (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*d->__nextchar == '\0') + ++d->optind; + + if (temp == NULL || c == ':') + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + int n; +#endif + + if (d->__posixly_correct) + { + /* 1003.2 specifies the format of this message. */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: illegal option -- %c\n"), + argv[0], c); +#else + fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); +#endif + } + else + { +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: invalid option -- %c\n"), + argv[0], c); +#else + fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); +#endif + } + +#if defined _LIBC && defined USE_IN_LIBIO + if (n >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#endif + } + d->optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, + _("%s: option requires an argument -- %c\n"), + argv[0], c) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); +#endif + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `d->optind' once; + increment it again when taking next ARGV-elt as argument. */ + d->optarg = argv[d->optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; + nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[d->optind]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[d->optind]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + d->optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); +#endif + } + + d->__nextchar += strlen (d->__nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen (d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + d->__nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else + d->optarg = NULL; + d->__nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option requires an argument -- %c\n"), + argv[0], c) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); +#endif + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + d->optarg = argv[d->optind++]; + d->__nextchar = NULL; + } + } + return c; + } +} + +int +_getopt_internal (int argc, char **argv, const char *optstring, + const struct option *longopts, int *longind, + int long_only, int posixly_correct) +{ + int result; + + getopt_data.optind = optind; + getopt_data.opterr = opterr; + + result = _getopt_internal_r (argc, argv, optstring, longopts, longind, + long_only, posixly_correct, &getopt_data); + + optind = getopt_data.optind; + optarg = getopt_data.optarg; + optopt = getopt_data.optopt; + + return result; +} + +/* glibc gets a LSB-compliant getopt. + Standalone applications get a POSIX-compliant getopt. */ +#if _LIBC +enum { POSIXLY_CORRECT = 0 }; +#else +enum { POSIXLY_CORRECT = 1 }; +#endif + +int +getopt (int argc, char *const *argv, const char *optstring) +{ + return _getopt_internal (argc, (char **) argv, optstring, NULL, NULL, 0, + POSIXLY_CORRECT); +} + + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == -1) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/Tools/unix/cpmtools/getopt1.c b/Tools/unix/cpmtools/getopt1.c new file mode 100644 index 00000000..7fa0c4e5 --- /dev/null +++ b/Tools/unix/cpmtools/getopt1.c @@ -0,0 +1,171 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98,2004,2006 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifdef _LIBC +# include +#else +# include "config.h" +# include "getopt_.h" +#endif +#include "getopt_int.h" + +#include + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (int argc, char *__getopt_argv_const *argv, const char *options, + const struct option *long_options, int *opt_index) +{ + return _getopt_internal (argc, (char **) argv, options, long_options, + opt_index, 0, 0); +} + +int +_getopt_long_r (int argc, char **argv, const char *options, + const struct option *long_options, int *opt_index, + struct _getopt_data *d) +{ + return _getopt_internal_r (argc, argv, options, long_options, opt_index, + 0, 0, d); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (int argc, char *__getopt_argv_const *argv, + const char *options, + const struct option *long_options, int *opt_index) +{ + return _getopt_internal (argc, (char **) argv, options, long_options, + opt_index, 1, 0); +} + +int +_getopt_long_only_r (int argc, char **argv, const char *options, + const struct option *long_options, int *opt_index, + struct _getopt_data *d) +{ + return _getopt_internal_r (argc, argv, options, long_options, opt_index, + 1, 0, d); +} + + +#ifdef TEST + +#include + +int +main (int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/Tools/unix/cpmtools/getopt_.h b/Tools/unix/cpmtools/getopt_.h new file mode 100644 index 00000000..615ef9a3 --- /dev/null +++ b/Tools/unix/cpmtools/getopt_.h @@ -0,0 +1,226 @@ +/* Declarations for getopt. + Copyright (C) 1989-1994,1996-1999,2001,2003,2004,2005,2006,2007 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +# define _GETOPT_H 1 +#endif + +/* Standalone applications should #define __GETOPT_PREFIX to an + identifier that prefixes the external functions and variables + defined in this header. When this happens, include the + headers that might declare getopt so that they will not cause + confusion if included after this file. Then systematically rename + identifiers so that they do not collide with the system functions + and variables. Renaming avoids problems with some compilers and + linkers. */ +#if defined __GETOPT_PREFIX && !defined __need_getopt +# include +# include +# include +# undef __need_getopt +# undef getopt +# undef getopt_long +# undef getopt_long_only +# undef optarg +# undef opterr +# undef optind +# undef optopt +# define __GETOPT_CONCAT(x, y) x ## y +# define __GETOPT_XCONCAT(x, y) __GETOPT_CONCAT (x, y) +# define __GETOPT_ID(y) __GETOPT_XCONCAT (__GETOPT_PREFIX, y) +# define getopt __GETOPT_ID (getopt) +# define getopt_long __GETOPT_ID (getopt_long) +# define getopt_long_only __GETOPT_ID (getopt_long_only) +# define optarg __GETOPT_ID (optarg) +# define opterr __GETOPT_ID (opterr) +# define optind __GETOPT_ID (optind) +# define optopt __GETOPT_ID (optopt) +#endif + +/* Standalone applications get correct prototypes for getopt_long and + getopt_long_only; they declare "char **argv". libc uses prototypes + with "char *const *argv" that are incorrect because getopt_long and + getopt_long_only can permute argv; this is required for backward + compatibility (e.g., for LSB 2.0.1). + + This used to be `#if defined __GETOPT_PREFIX && !defined __need_getopt', + but it caused redefinition warnings if both unistd.h and getopt.h were + included, since unistd.h includes getopt.h having previously defined + __need_getopt. + + The only place where __getopt_argv_const is used is in definitions + of getopt_long and getopt_long_only below, but these are visible + only if __need_getopt is not defined, so it is quite safe to rewrite + the conditional as follows: +*/ +#if !defined __need_getopt +# if defined __GETOPT_PREFIX +# define __getopt_argv_const /* empty */ +# else +# define __getopt_argv_const const +# endif +#endif + +/* If __GNU_LIBRARY__ is not already defined, either we are being used + standalone, or this is the first header included in the source file. + If we are being used with glibc, we need to include , but + that does not exist if we are standalone. So: if __GNU_LIBRARY__ is + not defined, include , which will pull in for us + if it's from glibc. (Why ctype.h? It's guaranteed to exist and it + doesn't flood the namespace with stuff the way some other headers do.) */ +#if !defined __GNU_LIBRARY__ +# include +#endif + +#ifndef __THROW +# ifndef __GNUC_PREREQ +# define __GNUC_PREREQ(maj, min) (0) +# endif +# if defined __cplusplus && __GNUC_PREREQ (2,8) +# define __THROW throw () +# else +# define __THROW +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +#ifndef __need_getopt +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ + const char *name; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 +#endif /* need getopt */ + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `-', then non-option arguments are treated as + arguments to the option '\1'. This behavior is specific to the GNU + `getopt'. If OPTS begins with `+', or POSIXLY_CORRECT is set in + the environment, then do not permute arguments. */ + +extern int getopt (int ___argc, char *const *___argv, const char *__shortopts) + __THROW; + +#ifndef __need_getopt +extern int getopt_long (int ___argc, char *__getopt_argv_const *___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind) + __THROW; +extern int getopt_long_only (int ___argc, char *__getopt_argv_const *___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind) + __THROW; + +#endif + +#ifdef __cplusplus +} +#endif + +/* Make sure we later can get all the definitions and declarations. */ +#undef __need_getopt + +#endif /* getopt.h */ diff --git a/Tools/unix/cpmtools/getopt_int.h b/Tools/unix/cpmtools/getopt_int.h new file mode 100644 index 00000000..401579fd --- /dev/null +++ b/Tools/unix/cpmtools/getopt_int.h @@ -0,0 +1,131 @@ +/* Internal declarations for getopt. + Copyright (C) 1989-1994,1996-1999,2001,2003,2004 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _GETOPT_INT_H +#define _GETOPT_INT_H 1 + +extern int _getopt_internal (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only, int __posixly_correct); + + +/* Reentrant versions which can handle parsing multiple argument + vectors at the same time. */ + +/* Data type for reentrant functions. */ +struct _getopt_data +{ + /* These have exactly the same meaning as the corresponding global + variables, except that they are used for the reentrant + versions of getopt. */ + int optind; + int opterr; + int optopt; + char *optarg; + + /* Internal members. */ + + /* True if the internal members have been initialized. */ + int __initialized; + + /* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + char *__nextchar; + + /* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters, or by calling getopt. + + PERMUTE is the default. We permute the contents of ARGV as we + scan, so that eventually all the non-options are at the end. + This allows options to be given in any order, even with programs + that were not written to expect this. + + RETURN_IN_ORDER is an option available to programs that were + written to expect options and other ARGV-elements in any order + and that care about the ordering of the two. We describe each + non-option ARGV-element as if it were the argument of an option + with character code 1. Using `-' as the first character of the + list of option characters selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + + enum + { + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER + } __ordering; + + /* If the POSIXLY_CORRECT environment variable is set + or getopt was called. */ + int __posixly_correct; + + + /* Handle permutation of arguments. */ + + /* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first + of them; `last_nonopt' is the index after the last of them. */ + + int __first_nonopt; + int __last_nonopt; + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + int __nonoption_flags_max_len; + int __nonoption_flags_len; +# endif +}; + +/* The initializer is necessary to set OPTIND and OPTERR to their + default values and to clear the initialization flag. */ +#define _GETOPT_DATA_INITIALIZER { 1, 1 } + +extern int _getopt_internal_r (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only, int __posixly_correct, + struct _getopt_data *__data); + +extern int _getopt_long_r (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + struct _getopt_data *__data); + +extern int _getopt_long_only_r (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, + int *__longind, + struct _getopt_data *__data); + +#endif /* getopt_int.h */ diff --git a/Tools/unix/cpmtools/mkfs.cpm.1 b/Tools/unix/cpmtools/mkfs.cpm.1 new file mode 100644 index 00000000..b31fb7ce --- /dev/null +++ b/Tools/unix/cpmtools/mkfs.cpm.1 @@ -0,0 +1,70 @@ +.TH MKFS.CPM 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME \"{{{roff}}}\"{{{ +mkfs.cpm \- make a CP/M file system +.\"}}} +.SH SYNOPSIS \"{{{ +.ad l +.B mkfs.cpm +.RB [ \-f +.IR format ] +.RB [ \-b +.IR boot ] +.RB [ \-L +.IR label ] +.RB [ \-t ] +.I image +.ad b +.\"}}} +.SH DESCRIPTION \"{{{ +\fBmkfs.cpm\fP makes a CP/M file system on an image file or device. +.\"}}} +.SH OPTIONS \"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-b\fP \fIbootblock\fP" +Write the contents of the file \fIbootblock\fP to the system tracks +instead of filling them with 0xe5. This option can be used up to four +times. The file contents (typically boot block, CCP, BDOS and BIOS) +are written to sequential sectors, padding with 0xe5 if needed. +.IP "\fB\-L\fP \fIlabel\fP" +Label the file system. This is only supported by CP/M Plus. +.IP "\fB\-t\fP" +Create time stamps. +.\"}}} +.SH "RETURN VALUE" \"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS \"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES \"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR fsck.cpm (1), +.IR cpmls (1), +.IR mkfs (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/mkfs.cpm.c b/Tools/unix/cpmtools/mkfs.cpm.c new file mode 100644 index 00000000..2c37bdfd --- /dev/null +++ b/Tools/unix/cpmtools/mkfs.cpm.c @@ -0,0 +1,234 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* #defines */ /*{{{*/ +#ifndef O_BINARY +#define O_BINARY 0 +#endif +/*}}}*/ + +/* mkfs -- make file system */ /*{{{*/ +static int mkfs(struct cpmSuperBlock *drive, const char *name, const char *format, const char *label, char *bootTracks, int timeStamps) +{ + /* variables */ /*{{{*/ + unsigned int i; + char buf[128]; + char firstbuf[128]; + int fd; + unsigned int bytes; + unsigned int trkbytes; + /*}}}*/ + + /* open image file */ /*{{{*/ + if ((fd = open(name, O_BINARY|O_CREAT|O_WRONLY, 0666)) < 0) + { + boo=strerror(errno); + return -1; + } + /*}}}*/ + /* write system tracks */ /*{{{*/ + /* this initialises only whole tracks, so it skew is not an issue */ + trkbytes=drive->secLength*drive->sectrk; + for (i=0; iboottrk; i+=drive->secLength) if (write(fd, bootTracks+i, drive->secLength)!=(ssize_t)drive->secLength) + { + boo=strerror(errno); + close(fd); + return -1; + } + /*}}}*/ + /* write directory */ /*{{{*/ + memset(buf,0xe5,128); + bytes=drive->maxdir*32; + if (bytes%trkbytes) bytes=((bytes+trkbytes)/trkbytes)*trkbytes; + if (timeStamps && (drive->type==CPMFS_P2DOS || drive->type==CPMFS_DR3)) buf[3*32]=0x21; + memcpy(firstbuf,buf,128); + if (drive->type==CPMFS_DR3) + { + time_t now; + struct tm *t; + int min,hour,days; + + firstbuf[0]=0x20; + for (i=0; i<11 && *label; ++i,++label) firstbuf[1+i]=toupper(*label&0x7f); + while (i<11) firstbuf[1+i++]=' '; + firstbuf[12]=timeStamps ? 0x11 : 0x01; /* label set and first time stamp is creation date */ + memset(&firstbuf[13],0,1+2+8); + if (timeStamps) + { + int year; + + /* Stamp label. */ + time(&now); + t=localtime(&now); + min=((t->tm_min/10)<<4)|(t->tm_min%10); + hour=((t->tm_hour/10)<<4)|(t->tm_hour%10); + for (year=1978,days=0; year<1900+t->tm_year; ++year) + { + days+=365; + if (year%4==0 && (year%100!=0 || year%400==0)) ++days; + } + days += t->tm_yday + 1; + firstbuf[24]=firstbuf[28]=days&0xff; firstbuf[25]=firstbuf[29]=days>>8; + firstbuf[26]=firstbuf[30]=hour; + firstbuf[27]=firstbuf[31]=min; + } + } + for (i=0; itype==CPMFS_P2DOS || drive->type==CPMFS_DR3)) /*{{{*/ + { + int offset,j; + struct cpmInode ino, root; + static const char sig[] = "!!!TIME"; + unsigned int records; + struct dsDate *ds; + struct cpmSuperBlock super; + const char *err; + + if ((err=Device_open(&super.dev,name,O_RDWR,NULL))) + { + fprintf(stderr,"%s: can not open %s (%s)\n",cmd,name,err); + exit(1); + } + cpmReadSuper(&super,&root,format); + + records=root.sb->maxdir/8; + if (!(ds=malloc(records*128))) + { + cpmUmount(&super); + return -1; + } + memset(ds,0,records*128); + offset=15; + for (i=0; ids=ds; + root.sb->dirtyDs=1; + cpmUmount(&super); + } + /*}}}*/ + + return 0; +} +/*}}}*/ + +const char cmd[]="mkfs.cpm"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + char *image; + const char *format; + int c,usage=0; + struct cpmSuperBlock drive; + struct cpmInode root; + const char *label="unlabeled"; + int timeStamps=0; + size_t bootTrackSize,used; + char *bootTracks; + const char *boot[4]={(const char*)0,(const char*)0,(const char*)0,(const char*)0}; + + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"b:f:L:th?"))!=EOF) switch(c) + { + case 'b': + { + if (boot[0]==(const char*)0) boot[0]=optarg; + else if (boot[1]==(const char*)0) boot[1]=optarg; + else if (boot[2]==(const char*)0) boot[2]=optarg; + else if (boot[3]==(const char*)0) boot[3]=optarg; + else usage=1; + break; + } + case 'f': format=optarg; break; + case 'L': label=optarg; break; + case 't': timeStamps=1; break; + case 'h': + case '?': usage=1; break; + } + + if (optind!=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-b boot] [-L label] [-t] image\n",cmd); + exit(1); + } + drive.dev.opened=0; + cpmReadSuper(&drive,&root,format); + bootTrackSize=drive.boottrk*drive.secLength*drive.sectrk; + if ((bootTracks=malloc(bootTrackSize))==(void*)0) + { + fprintf(stderr,"%s: can not allocate boot track buffer: %s\n",cmd,strerror(errno)); + exit(1); + } + memset(bootTracks,0xe5,bootTrackSize); + used=0; + for (c=0; c<4 && boot[c]; ++c) + { + int fd; + size_t size; + + if ((fd=open(boot[c],O_BINARY|O_RDONLY))==-1) + { + fprintf(stderr,"%s: can not open %s: %s\n",cmd,boot[c],strerror(errno)); + exit(1); + } + size=read(fd,bootTracks+used,bootTrackSize-used); +#if 0 + fprintf(stderr,"%d %04x %s\n",c,used+0x800,boot[c]); +#endif + if (size%drive.secLength) size=(size|(drive.secLength-1))+1; + used+=size; + close(fd); + } + if (mkfs(&drive,image,format,label,bootTracks,timeStamps)==-1) + { + fprintf(stderr,"%s: can not make new file system: %s\n",cmd,boo); + exit(1); + } + else exit(0); +} +/*}}}*/ diff --git a/Tools/unix/uz80as/Makefile b/Tools/unix/uz80as/Makefile new file mode 100644 index 00000000..32e4f46d --- /dev/null +++ b/Tools/unix/uz80as/Makefile @@ -0,0 +1,68 @@ +# =========================================================================== +# uz80as, an assembler for the Zilog Z80 and several other microprocessors. +# =========================================================================== + +DEST = ../../`uname` +CC = gcc +CFLAGS = -g + +OBJECTS = ngetopt.o main.o options.o \ + utils.o err.o incl.o sym.o \ + expr.o exprint.o pp.o list.o \ + prtable.o uz80as.o targets.o \ + z80.o gbcpu.o \ + dp2200.o i4004.o \ + i8008.o i8048.o \ + i8051.o i8080.o \ + mos6502.o mc6800.o + +SOURCES = \ + config.h \ + ngetopt.c ngetopt.h \ + main.c \ + options.c options.h \ + utils.c utils.h \ + err.c err.h \ + incl.c incl.h \ + sym.c sym.h \ + expr.c expr.h \ + exprint.c exprint.h \ + pp.c pp.h \ + list.c list.h \ + prtable.c prtable.h \ + uz80as.c uz80as.h \ + targets.c targets.h \ + z80.c \ + gbcpu.c \ + dp2200.c \ + i4004.c \ + i8008.c \ + i8048.c \ + i8051.c \ + i8080.c \ + mos6502.c \ + mc6800.c + +all: uz80as + +$(DEST): + mkdir -p $(DEST) + +install: uz80as $(DEST) + cp uz80as $(DEST) + +clobber: clean + -rm -f uz80as $(DEST)/uz80as + +clean: + -rm -f $(OBJECTS) + +uz80as: $(OBJECTS) + $(CC) $(CFLAGS) -o uz80as $(OBJECTS) + +test: test.asm uz80as + ./uz80as test.asm + cat test.lst + +.c.o: + $(CC) $(CFLAGS) -I. -c $< -o $@ diff --git a/Tools/unix/uz80as/config.h b/Tools/unix/uz80as/config.h new file mode 100644 index 00000000..cca14cbd --- /dev/null +++ b/Tools/unix/uz80as/config.h @@ -0,0 +1,29 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Years of copyright */ +#define COPYRIGHT_YEARS "2018" + +/* Name of package */ +#define PACKAGE "uz80as" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "jorge.giner@hotmail.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "uz80as" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "uz80as 1.10" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "uz80as" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "https://jorgicor.niobe.org/uz80as" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.10" + +/* Version number of package */ +#define VERSION "1.10" diff --git a/Tools/unix/uz80as/dp2200.c b/Tools/unix/uz80as/dp2200.c new file mode 100644 index 00000000..c6599247 --- /dev/null +++ b/Tools/unix/uz80as/dp2200.c @@ -0,0 +1,208 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Datapoint 2200. + * =========================================================================== + */ + +/* + * Datapoint 2200 Version I, 2K to 8K mem (program counter 13 bits). + * Datapoint 2200 Version II, 2K to 16K mem (protram counter 14 bits). + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ADA,ADB,ADC,ADD,ADH,ADL,ADM, + * ACA,ACB,ACC,ACD,ACH,ACL,ACM, + * SUA,SUB,SUC,SUD,SUH,SUL,SUM, + * SBA,SBB,SBC,SBD,SBH,SBL,SBM, + * NDA,NDB,NDC,NDD,NDH,NDL,NDM, + * XRA,XRB,XRC,XRD,XRH,XRL,XRM, + * ORA,ORB,ORC,ORD,ORH,ORL,ORM, + * CPA,CPB,CPC,CPD,CPH,CPL,CPM + * c: NOP,LAB,LAC,LAD,LAE,LAH,LAL,LAM, + * LBA,LBC,LBD,LBE,LBH,LBL,LBM, + * LCA,LCB,LCD,LCE,LCH,LCL,LCM, + * LDA,LDB,LDC,LDE,LDH,LDL,LDM, + * LEA,LEB,LEC,LED,LEH,LEL,LEM, + * LHA,LHB,LHC,LHD,LHE,LHL,LHM, + * LLA,LLB,LLC,LLD,LLE,LLH,LLM, + * LMA,LMB,LMC,LMD,LME,LMH,LML,HALT + * d: ADR,STATUS,DATA,WRITE,COM1,COM2,COM3,COM4 + * BEEP,CLICK,DECK1,DECK2, + * RBK,WBK,BSP,SF,SB,REWND,TSTOP + * e: RFC,RFS,RTC,RTS,RFZ,RFP,RTZ,RTP + * f: JFC,JFZ,JFS,JFP,JTC,JTZ,JTS,JTP + * g: AD,SU,ND,OR,AC,SB,XR,CP + * h: LA,LB,LC,LD,LE,LH,LL + * i: CFC,CFZ,CFS,CFP,CTC,CTZ,CTS,CTP + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 1) + lastbyte + * g: (op << 4) | lastbyte + */ + +const struct matchtab s_matchtab_dp2200[] = { + { "SLC", "02.", 3, 0 }, + { "SRC", "0A.", 3, 0 }, + { "RETURN", "07.", 3, 0 }, + { "INPUT", "41.", 3, 0 }, + { "b", "80c0.", 3, 0 }, + { "c", "C0c0.", 3, 0 }, + { "EX d", "51f0.", 3, 0 }, + { "e", "03b0.", 3, 0 }, + { "g a", "04b0.d1.", 3, 0, "e8" }, + { "h a", "06b0.d1.", 3, 0, "e8"}, + { "f a", "40b0.e1", 3, 0 }, + { "i a", "42b0.e1", 3, 0 }, + { "JMP a", "44.e0", 3, 0 }, + { "CALL a", "46.e0", 3, 0 }, + /* version II */ + { "BETA", "10.", 2, 0 }, + { "DI", "20.", 2, 0 }, + { "POP", "30.", 2, 0 }, + { "ALPHA", "18.", 2, 0 }, + { "EI", "28.", 2, 0 }, + { "PUSH", "38.", 2, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"ADA", "ADB", "ADC", "ADD", "ADE", "ADH", "ADL", "ADM", +"ACA", "ACB", "ACC", "ACD", "ACE", "ACH", "ACL", "ACM", +"SUA", "SUB", "SUC", "SUD", "SUE", "SUH", "SUL", "SUM", +"SBA", "SBB", "SBC", "SBD", "SBE", "SBH", "SBL", "SBM", +"NDA", "NDB", "NDC", "NDD", "NDE", "NDH", "NDL", "NDM", +"XRA", "XRB", "XRC", "XRD", "XRE", "XRH", "XRL", "XRM", +"ORA", "ORB", "ORC", "ORD", "ORE", "ORH", "ORL", "ORM", +"CPA", "CPB", "CPC", "CPD", "CPE", "CPH", "CPL", "CPM", +NULL }; + +static const char *const cval[] = { +"NOP", "LAB", "LAC", "LAD", "LAE", "LAH", "LAL", "LAM", +"LBA", "", "LBC", "LBD", "LBE", "LBH", "LBL", "LBM", +"LCA", "LCB", "", "LCD", "LCE", "LCH", "LCL", "LCM", +"LDA", "LDB", "LDC", "", "LDE", "LDH", "LDL", "LDM", +"LEA", "LEB", "LEC", "LED", "", "LEH", "LEL", "LEM", +"LHA", "LHB", "LHC", "LHD", "LHE", "", "LHL", "LHM", +"LLA", "LLB", "LLC", "LLD", "LLE", "LLH", "", "LLM", +"LMA", "LMB", "LMC", "LMD", "LME", "LMH", "LML", "HALT", +NULL }; + +static const char *const dval[] = { +"ADR", "STATUS", "DATA", "WRITE", "COM1", "COM2", "COM3", "COM4", +"", "", "", "", "BEEP", "CLICK", "DECK1", "DECK2", +"RBK", "WBK", "", "BSP", "SF", "SB", "REWND", "TSTOP", +NULL }; + +static const char *const eval[] = { "RFC", "RFZ", "RFS", "RFP", + "RTC", "RTZ", "RTS", "RTP", + NULL }; + +static const char *const fval[] = { "JFC", "JFZ", "JFS", "JFP", + "JTC", "JTZ", "JTS", "JTP", + NULL }; + +static const char *const gval[] = { "AD", "AC", "SU", "SB", + "ND", "XR", "OR", "CP", + NULL }; + +static const char *const hval[] = { "LA", "LB", "LC", "LD", + "LE", "LH", "LL", + NULL }; + +static const char *const ival[] = { "CFC", "CFZ", "CFS", "CFP", + "CTC", "CTZ", "CTS", "CTP", + NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival +}; + +static int match_dp2200(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'i') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_dp2200(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b += (vs[i] << 1); break; + case 'g': b |= (vs[i] << 4); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_dp2200(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_dp2200(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'n') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_dp2200 = { + .id = "dp2200", + .descr = "Datapoint 2200 Version I", + .matcht = s_matchtab_dp2200, + .matchf = match_dp2200, + .genf = gen_dp2200, + .pat_char_rewind = pat_char_rewind_dp2200, + .pat_next_str = pat_next_str_dp2200, + .mask = 1 +}; + +const struct target s_target_dp2200ii = { + .id = "dp2200ii", + .descr = "Datapoint 2200 Version II", + .matcht = s_matchtab_dp2200, + .matchf = match_dp2200, + .genf = gen_dp2200, + .pat_char_rewind = pat_char_rewind_dp2200, + .pat_next_str = pat_next_str_dp2200, + .mask = 2 +}; diff --git a/Tools/unix/uz80as/err.c b/Tools/unix/uz80as/err.c new file mode 100644 index 00000000..b6745ae8 --- /dev/null +++ b/Tools/unix/uz80as/err.c @@ -0,0 +1,196 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Error reporting. + * =========================================================================== + */ + +#include "config.h" +#include "err.h" +#include "incl.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef STDARG_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +/* Max number of errors before halt. */ +#define MAXERR 64 + +int s_nerrors; + +static void eprfl(void) +{ + fprintf(stderr, "%s:%d: ", curfile()->name, curfile()->linenum); +} + +static void eprwarn(void) +{ + fputs(_("warning:"), stderr); + fputc(' ', stderr); +} + +/* Print the characters in [p, q[ to stderr. */ +void echars(const char *p, const char *q) +{ + while (*p != '\0' && p != q) { + fputc(*p, stderr); + p++; + } +} + +/* + * Print a space, an opening parenthesis, the characters in [p, q[, + * and a closing parenthesis to stderr. + */ +void epchars(const char *p, const char *q) +{ + fputs(" (", stderr); + echars(p, q); + fputs(")", stderr); +} + +/* + * Increments the number of errors, and exit with failure if + * maximum number of errors allowed is reached. + */ +void newerr(void) +{ + s_nerrors++; + if (s_nerrors >= MAXERR) { + eprogname(); + fprintf(stderr, _("exiting: too many errors")); + enl(); + exit(EXIT_FAILURE); + } +} + +static void evprint(int warn, const char *ecode, va_list args) +{ + if (nfiles() > 0) + eprfl(); + else + eprogname(); + + if (warn) + eprwarn(); + + assert(ecode != NULL); + vfprintf(stderr, ecode, args); +} + +/* Prints only the printable characters, the rst as space. */ +static void eprint_printable(const char *p) +{ + for (; *p != '\0'; p++) { + if (isprint(*p)) + putc(*p, stderr); + else + putc(' ', stderr); + } +} + +/* Prints the line and a marker pointing to the charcater q inside line. */ +void eprcol(const char *line, const char *q) +{ + putc(' ', stderr); + eprint_printable(line); + fputs("\n ", stderr); + while (line != q) { + putc(' ', stderr); + line++; + } + fputs("^\n", stderr); +} + +/* + * Like fprintf but prints to stderr. + * If we are parsing any file (incl.c), print first the file and the line. + * If not, print first the program name. + */ +void eprint(const char *ecode, ...) +{ + va_list args; + + va_start(args, ecode); + evprint(0, ecode, args); + va_end(args); +} + +/* Same as eprint, but print "warning: " before ecode str. */ +void wprint(const char *ecode, ...) +{ + va_list args; + + va_start(args, ecode); + evprint(1, ecode, args); + va_end(args); +} + +/* Print \n on stderr. */ +void enl(void) +{ + fputc('\n', stderr); +} + +/* Print the program name on stderr. */ +void eprogname(void) +{ + fprintf(stderr, PACKAGE": "); +} + +/* Call malloc, but if no memory, print that error and exit with failure. */ +void *emalloc(size_t n) +{ + void *p; + + if ((p = malloc(n)) == NULL) { + eprint(_("malloc fail\n")); + exit(EXIT_FAILURE); + } + // printf("emalloc: %d = %x\n", n, p); + return p; +} + +/* Call realloc, but if no memory, print that error and exit with failure. */ +void *erealloc(void *p, size_t n) +{ + // void *q = p; + if ((p = realloc(p, n)) == NULL) { + eprint(_("realloc fail\n")); + exit(EXIT_FAILURE); + } + // printf("erealloc: %x %d = %x\n", q, n, p); + return p; +} + +/* Call fopen, but if any error, print it and exit with failure. */ +FILE *efopen(const char *fname, const char *ops) +{ + FILE *fp; + + if ((fp = fopen(fname, ops)) == NULL) { + eprint(_("cannot open file %s\n"), fname); + exit(EXIT_FAILURE); + } + return fp; +} diff --git a/Tools/unix/uz80as/err.h b/Tools/unix/uz80as/err.h new file mode 100644 index 00000000..86d8e1dc --- /dev/null +++ b/Tools/unix/uz80as/err.h @@ -0,0 +1,32 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Error reporting. + * =========================================================================== + */ + +#ifndef ERR_H +#define ERR_H + +#ifndef STDIO_H +#define STDIO_H +#include +#endif + +#define _(str) (str) + +extern int s_nerrors; + +void newerr(void); +void eprogname(void); +void echars(const char *p, const char *q); +void epchars(const char *p, const char *q); +void eprint(const char *ecode, ...); +void wprint(const char *ecode, ...); +void eprcol(const char *line, const char *q); +void enl(void); +void *emalloc(size_t n); +void *erealloc(void *p, size_t n); +FILE *efopen(const char *fname, const char *ops); + +#endif diff --git a/Tools/unix/uz80as/expr.c b/Tools/unix/uz80as/expr.c new file mode 100644 index 00000000..2897461f --- /dev/null +++ b/Tools/unix/uz80as/expr.c @@ -0,0 +1,445 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression parsing. + * =========================================================================== + */ + +#include "config.h" +#include "expr.h" +#include "utils.h" +#include "err.h" +#include "sym.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef LIMITS_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +/* Max nested expressions. */ +#define ESTKSZ 16 +#define ESTKSZ2 (ESTKSZ*2) + +/* Return -1 on syntax error. + * *p must be a digit already. + * *q points to one past the end of the number without suffix. + */ +static int takenum(const char *p, const char *q, int radix) +{ + int k, n; + + n = 0; + while (p != q) { + k = hexval(*p); + p++; + if (k >= 0 && k < radix) + n = n * radix + k; + else + return -1; + } + return n; +} + +/* Go to the end of a number (advance all digits or letters). */ +static const char *goendnum(const char *p) +{ + const char *q; + + for (q = p; isalnum(*q); q++) + ; + return q; +} + +/* + * Returns NULL on error. + * '*p' must be a digit already. + */ +static const char *getnum(const char *p, int *v) +{ + int n; + char c; + const char *q; + + assert(isdigit(*p)); + + n = 0; + q = goendnum(p) - 1; + if (isalpha(*q)) { + c = toupper(*q); + if (c == 'H') { + n = takenum(p, q, 16); + } else if (c == 'D') { + n = takenum(p, q, 10); + } else if (c == 'O') { + n = takenum(p, q, 8); + } else if (c == 'B') { + n = takenum(p, q, 2); + } else { + return NULL; + } + } else { + n = takenum(p, q + 1, 10); + } + + if (n < 0) + return NULL; + + *v = n; + return q + 1; +} + +/* + * Gets a number that was prefixed. + * Returns NULL on error. + */ +static const char *getpnum(const char *p, int radix, int *v) +{ + const char *q; + int n; + + q = goendnum(p); + n = takenum(p, q, radix); + if (n < 0) + return NULL; + *v = n; + return q; +} + +/* Left shift */ +static int shl(int r, int n) +{ + n &= int_precission(); + return r << n; +} + + +/* Portable arithmetic right shift. */ +static int ashr(int r, int n) +{ + n &= int_precission(); + if (r & INT_MIN) { + return ~(~r >> n); + } else { + return r >> n; + } +} + +/* Parses expression pointed by 'p'. + * If success, returns pointer to the end of parsed expression, and + * 'v' contains the calculated value of the expression. + * Returns NULL if a syntactic error has occurred. + * Operators are evaluated left to right. + * To allow precedence use parenthesis. + * 'linepc' is the program counter to consider when we find the $ current + * pointer location symbol ($). + * 'allowfr' stands for 'allow forward references'. We will issue an error + * if we find a label that is not defined. + * 'ecode' will be valid if NULL is returned. NULL can be passed as ecode. + * 'ep' is the pointer to the position where the error ocurred. NULL can be + * passed as ep. + */ +const char *expr(const char *p, int *v, int linepc, int allowfr, + enum expr_ecode *ecode, const char **ep) +{ + int si, usi; + const char *q; + char last; + int stack[ESTKSZ2]; + int uopstk[ESTKSZ]; + int r, n; + struct sym *sym; + int err; + enum expr_ecode ec; + + ec = EXPR_E_NO_EXPR; + err = 0; + usi = 0; + si = 0; + r = 0; + last = 'V'; /* first void */ +loop: + p = skipws(p); + if (*p == '(') { + if (last == 'n') { + goto end; + } else { + if (si >= ESTKSZ2) { + eprint(_("expression too complex\n")); + exit(EXIT_FAILURE); + } + stack[si++] = last; + stack[si++] = r; + p++; + r = 0; + last = 'v'; /* void */ + } + } else if (*p == ')') { + if (last != 'n') { + ec = EXPR_E_CPAR; + goto esyntax; + } else if (si == 0) { + goto end; + } else { + p++; + n = r; + r = stack[--si]; + last = (char) stack[--si]; + goto oper; + } + } else if (*p == '+') { + p++; + if (last == 'n') + last = '+'; + } else if (*p == '-') { + if (last == 'n') { + p++; + last = '-'; + } else { + goto uoper; + } + } else if (*p == '~') { + goto uoper; + } else if (*p == '!') { + if (*(p + 1) == '=') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } else { + p += 2; + last = 'N'; + } + } else { + goto uoper; + } + } else if (*p == '*') { + if (last == 'n') { + last = *p++; + } else { + p++; + n = linepc; + goto oper; + } + } else if (*p == '/' || *p == '&' || *p == '|' + || *p == '^') + { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } else { + last = *p++; + } + } else if (*p == '>') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } + p++; + if (*p == '=') { + last = 'G'; + p++; + } else if (*p == '>') { + last = 'R'; + p++; + } else { + last = '>'; + } + } else if (*p == '<') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } + p++; + if (*p == '=') { + last = 'S'; + p++; + } else if (*p == '<') { + last = 'L'; + p++; + } else { + last = '<'; + } + } else if (*p == '=') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } + p++; + if (*p == '=') + p++; + last = '='; + } else if (*p == '\'') { + if (last == 'n') + goto end; + p++; + n = *p++; + if (*p != '\'') { + ec = EXPR_E_CHAR; + goto esyntax; + } + p++; + goto oper; + } else if (*p == '$') { + if (last == 'n') + goto end; + p++; + if (hexval(*p) < 0) { + n = linepc; + goto oper; + } + q = getpnum(p, 16, &n); + if (q == NULL) { + p--; + ec = EXPR_E_HEX; + goto esyntax; + } + p = q; + goto oper; + } else if (*p == '@') { + if (last == 'n') + goto end; + p++; + q = getpnum(p, 8, &n); + if (q == NULL) { + p--; + ec = EXPR_E_OCTAL; + goto esyntax; + } + p = q; + goto oper; + } else if (*p == '%') { + if (last == 'n') { + last = *p; + p++; + } else { + p++; + q = getpnum(p, 2, &n); + if (q == NULL) { + ec = EXPR_E_BIN; + goto esyntax; + } + p = q; + goto oper; + } + } else if (isdigit(*p)) { + if (last == 'n') + goto end; + q = getnum(p, &n); + if (q == NULL) { + ec = EXPR_E_DEC; + goto esyntax; + } + p = q; + goto oper; + } else if (isidc0(*p)) { + if (last == 'n') + goto end; + q = p; + while (isidc(*p)) + p++; + sym = lookup(q, p, 0, 0); + if (sym == NULL) { + n = 0; + if (!allowfr) { + err = 1; + eprint(_("undefined label")); + epchars(q, p); + enl(); + newerr(); + } + } else { + n = sym->val; + } + goto oper; + } else if (last == 'V') { + goto esyntax; + } else if (last != 'n') { + ec = EXPR_E_SYNTAX; + goto esyntax; + } else { +end: if (v != NULL) + *v = r; + return p; + } + goto loop; +uoper: + if (last == 'n') + goto end; + if (usi >= ESTKSZ) { + eprint(_("expression too complex\n")); + exit(EXIT_FAILURE); + } + uopstk[usi++] = *p++; + goto loop; +oper: + while (usi > 0) { + usi--; + switch (uopstk[usi]) { + case '~': n = ~n; break; + case '-': n = -n; break; + case '!': n = !n; break; + } + } + switch (last) { + case 'V': r = n; break; + case 'v': r = n; break; + case '+': r += n; break; + case '-': r -= n; break; + case '*': r *= n; break; + case '&': r &= n; break; + case '|': r |= n; break; + case '^': r ^= n; break; + case '=': r = r == n; break; + case '<': r = r < n; break; + case '>': r = r > n ; break; + case 'G': r = r >= n; break; + case 'S': r = r <= n; break; + case 'N': r = r != n; break; + /* This would be logical right shift: + * case 'R': r = (unsigned int) r >> n; break; + */ + case 'R': r = ashr(r, n); break; + case 'L': r = shl(r, n); break; + case '~': r = ~n; break; + case '%': + if (n != 0) { + r %= n; + } else if (!err && !allowfr) { + err = 1; + eprint(_("modulo by zero\n")); + exit(EXIT_FAILURE); + } + break; + case '/': + if (n != 0) { + r /= n; + } else if (!err && !allowfr) { + err = 1; + eprint(_("division by zero\n")); + exit(EXIT_FAILURE); + } + break; + } + last = 'n'; + goto loop; +esyntax: + if (ecode != NULL) + *ecode = ec; + if (ep != NULL) + *ep = p; + return NULL; +} diff --git a/Tools/unix/uz80as/expr.h b/Tools/unix/uz80as/expr.h new file mode 100644 index 00000000..e1e55fc2 --- /dev/null +++ b/Tools/unix/uz80as/expr.h @@ -0,0 +1,26 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression parsing. + * =========================================================================== + */ + +#ifndef EXPR_H +#define EXPR_H + +enum expr_ecode { + EXPR_E_NO_EXPR, /* There was no expression parsed. */ + EXPR_E_SYNTAX, /* Syntax error. */ + EXPR_E_CPAR, + EXPR_E_OPER, + EXPR_E_CHAR, + EXPR_E_HEX, + EXPR_E_OCTAL, + EXPR_E_BIN, + EXPR_E_DEC, +}; + +const char *expr(const char *p, int *v, int linepc, int allowfr, + enum expr_ecode *ecode, const char **ep); + +#endif diff --git a/Tools/unix/uz80as/exprint.c b/Tools/unix/uz80as/exprint.c new file mode 100644 index 00000000..67699636 --- /dev/null +++ b/Tools/unix/uz80as/exprint.c @@ -0,0 +1,32 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression error reporting. + * =========================================================================== + */ + +#include "config.h" +#include "exprint.h" +#include "err.h" + +static const char *expr_get_error_str(enum expr_ecode ecode) +{ + switch (ecode) { + case EXPR_E_NO_EXPR: return _("expression expected\n"); + case EXPR_E_SYNTAX: return _("syntax error in expression\n"); + case EXPR_E_CPAR: return _("unexpected ')'\n"); + case EXPR_E_OPER: return _("misplaced operator\n"); + case EXPR_E_CHAR: return _("invalid character code\n"); + case EXPR_E_HEX: return _("invalid hexadecimal constant\n"); + case EXPR_E_OCTAL: return _("invalid octal constant\n"); + case EXPR_E_BIN: return _("invalid binary constant\n"); + case EXPR_E_DEC: return _("invalid decimal constant\n"); + default: return "\n"; + } +} + +void exprint(enum expr_ecode ecode, const char *pline, const char *ep) +{ + eprint(expr_get_error_str(ecode)); + eprcol(pline, ep); +} diff --git a/Tools/unix/uz80as/exprint.h b/Tools/unix/uz80as/exprint.h new file mode 100644 index 00000000..00b43c2f --- /dev/null +++ b/Tools/unix/uz80as/exprint.h @@ -0,0 +1,17 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression error reporting. + * =========================================================================== + */ + +#ifndef EXPRINT_H +#define EXPRINT_H + +#ifndef EXPR_H +#include "expr.h" +#endif + +void exprint(enum expr_ecode ecode, const char *pline, const char *ep); + +#endif diff --git a/Tools/unix/uz80as/gbcpu.c b/Tools/unix/uz80as/gbcpu.c new file mode 100644 index 00000000..d6b347f3 --- /dev/null +++ b/Tools/unix/uz80as/gbcpu.c @@ -0,0 +1,301 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Sharp LR35902 (Nintendo Gameboy CPU). + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +#if 0 +Opcode Z80 GMB + --------------------------------------- + 08 EX AF,AF LD (nn),SP + 10 DJNZ PC+dd STOP + 22 LD (nn),HL LDI (HL),A + 2A LD HL,(nn) LDI A,(HL) + 32 LD (nn),A LDD (HL),A + 3A LD A,(nn) LDD A,(HL) + D3 OUT (n),A - + D9 EXX RETI + DB IN A,(n) - + DD - + E0 RET PO LD (FF00+n),A + E2 JP PO,nn LD (FF00+C),A + E3 EX (SP),HL - + E4 CALL PO,nn - + E8 RET PE ADD SP,dd + EA JP PE,nn LD (nn),A + EB EX DE,HL - + EC CALL PE,nn - + ED - + F0 RET P LD A,(FF00+n) + F2 JP P,nn LD A,(FF00+C) + F4 CALL P,nn - + F8 RET M LD HL,SP+dd + FA JP M,nn LD A,(nn) + FC CALL M,nn - + FD - + CB3X SLL r/(HL) SWAP r/(HL) +#endif + +/* pat: + * a: expr + * b: B,C,D,E,H,L,A + * c: * + * d: BC,DE,HL,SP + * e: * + * f: BC,DE,HL,AF + * g: ADD,ADC,SUB,SBC,AND,XOR,OR,CP + * h: INC,DEC + * i: * + * j: * + * k: * + * l: BIT,RES,SET + * m: * + * n: NZ,Z,NC,C + * o: RLC,RRC,RL,RR,SLA,SRA,SWAP,SRL + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 4) | lastbyte + * g: (op << 6) | lastbyte + * h: if op >= FF00 output last byte and then op as 8 bit value; + * else output (lastbyte | 0x0A) and output op as word + * (no '.' should follow) + * i: relative jump to op + * j: possible value to RST + * k: possible value to IM + * l: * + * m: check arithmetic used with A register + * n: check arithmetic used without A register + */ + +const struct matchtab s_matchtab_gbcpu[] = { + { "LD b,b", "40b0c1.", 1, 0 }, + { "LD b,(HL)", "46b0.", 1, 0 }, + { "LD A,(C)", "F2.", 1, 0 }, // * LD A,(FF00+C) + { "LD A,(BC)", "0A.", 1, 0 }, + { "LD A,(DE)", "1A.", 1, 0 }, + { "LD A,(HLI)", "2A.", 1, 0 }, // * + { "LD A,(HLD)", "3A.", 1, 0 }, // * + { "LD A,(a)", "F0h0", 1, 0 }, // * LD A,(nn) & LD A,(FF00+n) + { "LD b,a", "06b0.d1.", 1, 0, "e8" }, + { "LD SP,HL", "F9.", 1, 0 }, + { "LDHL SP,a", "F8.d0.", 1, 0, "e8" }, // * LD HL,SP+n + { "LD d,a", "01f0.e1", 1, 0 }, + { "LD (C),A", "E2.", 1, 0 }, // * LD (FF00+C),A + { "LD (HL),b", "70c0.", 1, 0 }, + { "LD (HL),a", "36.d0.", 1, 0, "e8" }, + { "LD (HLI),A", "22.", 1, 0 }, // * + { "LD (HLD),A", "32.", 1, 0 }, // * + { "LD (BC),A", "02.", 1, 0 }, + { "LD (DE),A", "12.", 1, 0 }, + { "LD (a),A", "E0h0", 1, 0 }, // * LD (nn),A & LD (FF00+n),A + { "LD (a),SP", "08.e0", 1, 0 }, // * + { "LDH A,(a)", "F0.d0.", 1, 0, "e8" }, // * LD A,(FF00+n) + { "LDH (a),A", "E0.d0.", 1, 0, "e8" }, // * LD (FF00+n),A + { "PUSH f", "C5f0.", 1, 0 }, + { "POP f", "C1f0.", 1, 0 }, + { "ADD HL,d", "09f0.", 1, 0 }, + { "ADD SP,a", "E8.d0.", 1, 0, "e8" }, // * + { "g A,b", "m080b0c1.", 1, 0 }, + { "g A,(HL)", "m086b0.", 1, 0 }, + { "g A,a", "m0C6b0.d1.", 1, 0, "e8" }, + { "g b", "n080b0c1.", 1, 0 }, + { "g (HL)", "n086b0.", 1, 0 }, + { "g a", "n0C6b0.d1.", 1, 0, "e8" }, + { "h b", "04b1c0.", 1, 0 }, + { "h (HL)", "34c0.", 1, 0 }, + { "INC d", "03f0.", 1, 0 }, + { "DEC d", "0Bf0.", 1, 0 }, + { "DAA", "27.", 1, 0 }, + { "CPL", "2F.", 1, 0 }, + { "CCF", "3F.", 1, 0 }, + { "SCF", "37.", 1, 0 }, + { "NOP", "00.", 1, 0 }, + { "HALT", "76.", 1, 0 }, + { "DI", "F3.", 1, 0 }, + { "EI", "FB.", 1, 0 }, + { "RLCA", "07.", 1, 0 }, + { "RLA", "17.", 1, 0 }, + { "RRCA", "0F.", 1, 0 }, + { "RRA", "1F.", 1, 0 }, + { "o b", "CB.00b0c1.", 1, 0 }, + { "o (HL)", "CB.06b0.", 1, 0 }, + { "l a,b", "CB.00g0b1c2.", 1, 0, "b3" }, + { "l a,(HL)", "CB.06g0b1.", 1, 0, "b3" }, + { "JP (HL)", "E9.", 1, 0 }, + { "JP n,a", "C2b0.e1", 1, 0 }, // * + { "JP a", "C3.e0", 1, 0 }, + { "JR n,a", "20b0.i1.", 1, 0, "r8" }, + { "JR a", "18.i0.", 1, 0, "r8" }, + { "STOP", "10.00.", 1, 0 }, // * + { "CALL n,a", "C4b0.e1", 1, 0 }, // * + { "CALL a", "CD.e0", 1, 0 }, + { "RETI", "D9.", 1, 0 }, // * + { "RET n", "C0b0.", 1, 0 }, + { "RET", "C9.", 1, 0 }, + { "RST a", "C7j0.", 1, 0, "ss" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { "B", "C", "D", "E", + "H", "L", "", "A", NULL }; +static const char *const dval[] = { "BC", "DE", "HL", "SP", NULL }; +static const char *const fval[] = { "BC", "DE", "HL", "AF", NULL }; +static const char *const gval[] = { "ADD", "ADC", "SUB", "SBC", + "AND", "XOR", "OR", "CP", NULL }; +static const char *const hval[] = { "INC", "DEC", NULL }; +static const char *const lval[] = { "", "BIT", "RES", "SET", NULL }; +static const char *const nval[] = { "NZ", "Z", "NC", "C", NULL }; +static const char *const oval[] = { "RLC", "RRC", "RL", "RR", + "SLA", "SRA", "SWAP", "SRL", NULL }; +static const char *const nullv[] = { NULL }; + +static const char *const *const valtab[] = { + bval, nullv, dval, nullv, fval, + gval, hval, nullv, nullv, nullv, + lval, nullv, nval, oval +}; + +static int match_gbcpu(char c, const char *p, const char **q) +{ + int v; + + switch (c) { + case 'b': + case 'd': + case 'f': + case 'g': + case 'h': + case 'l': + case 'n': + case 'o': + v = mreg(p, valtab[(int) (c - 'b')], q); + break; + default: + v = -1; + } + + return v; +} + +static int gen_gbcpu(int *eb, char p, const int *vs, int i, int savepc) +{ + int w, b; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 4); break; + case 'g': b |= (vs[i] << 6); break; + case 'h': w = vs[i] & 0xffff; + if (w >= 0xff00) { + genb(b, s_pline_ep); + b = 0; + genb(w & 0xff, s_pline_ep); + } else { + b |= 0x0A; + genb(b, s_pline_ep); + b = 0; + genb(w & 0xff, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'i': b = (vs[i] - savepc - 2); break; + case 'j': if (s_pass > 0 && (vs[i] & ~56) != 0) { + eprint(_("invalid RST argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'k': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 2)) { + eprint(_("invalid IM argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b = 0x46; + if (vs[i] == 1) + b = 0x56; + else if (vs[i] == 2) + b = 0x5E; + break; + case 'm': if (s_pass == 0 && !s_extended_op) { + if (vs[i] != 0 && vs[i] != 1 && vs[i] != 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + case 'n': if (s_pass == 0 && !s_extended_op) { + if (vs[i] == 0 || vs[i] == 1 || vs[i] == 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_gbcpu(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_gbcpu(void) +{ + const char *s; + + switch (s_pat_char) { + case 'b': + case 'd': + case 'f': + case 'g': + case 'h': + case 'l': + case 'n': + case 'o': + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + break; + default: + s = NULL; + } + + return s; +}; + +const struct target s_target_gbcpu = { + .id = "gbcpu", + .descr = "Sharp LR35902 (Nintendo Gameboy CPU)", + .matcht = s_matchtab_gbcpu, + .matchf = match_gbcpu, + .genf = gen_gbcpu, + .pat_char_rewind = pat_char_rewind_gbcpu, + .pat_next_str = pat_next_str_gbcpu, + .mask = 1 +}; diff --git a/Tools/unix/uz80as/i4004.c b/Tools/unix/uz80as/i4004.c new file mode 100644 index 00000000..98649abd --- /dev/null +++ b/Tools/unix/uz80as/i4004.c @@ -0,0 +1,189 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 4004. + * Intel 4040. + * =========================================================================== + */ + +/* Intel 4004. Max. memory 4K (12 bit addresses). + * Intel 4040. Max. memory 8K (13 bit addresses). + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ADD,SUB,LD,XCH,BBL,LDM + * c: WRM,WMP,WRR,WPM,WR0,WR1,WR2,WR3 + * SBM,RDM,RDR,ADM,RD0,RD1,RD2,RD3 + * d: CLB,CLC,IAC,CMC,CMA,RAL,RAR,TCC, + * DAC,TCS,STC,DAA,KBP,DCL, + * e: HLT,BBS,LCR,OR4,OR5,AN6,AN7 + * DB0,DB1,SB0,SB1,EIN,DIN,RPM + * f: 0P,1P,2P,3P,4P,5P,6P,7P + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: op | lastbyte, op in [0-15] + * g: op | lastbyte, op in [0,2,4,6,8,10,12,14] + * h: output (op & 0xff0000 >> 8) | lastbyte; + * then ouput op as 8 bit value + * i: (op << 4) | lastbyte + * j: (op << 1) | lastbyte + */ + +static const struct matchtab s_matchtab_i4004[] = { + { "NOP", "00.", 1, 0 }, + { "JCN a,a", "10f0.d1.", 1, 0, "b4e8" }, + { "FIM f,a", "20j0.d1.", 1, 0, "e8" }, + { "FIM a,a", "20g0.d1.", 1, 0, "ppe8" }, + { "SRC f", "21j0.", 1, 0 }, + { "SRC a", "21g0.", 1, 0, "pp" }, + { "FIN f", "30j0.", 1, 0 }, + { "FIN a", "30g0.", 1, 0, "pp" }, + { "JIN f", "31j0.", 1, 0 }, + { "JIN a", "31g0.", 1, 0, "pp" }, + { "JUN a", "40h0", 1, 0 }, + { "JMS a", "50h0", 1, 0 }, + { "INC a", "60f0.", 1, 0, "b4" }, + { "ISZ a,a", "70f0.d1.", 1, 0, "b4e8" }, + { "b a", "80i0f1.", 1, 0, "b4" }, + { "c", "E0c0.", 1, 0 }, + { "d", "F0c0.", 1, 0 }, + { "e", "00c0.", 2, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"ADD", "SUB", "LD", "XCH", "BBL", "LDM", +NULL }; + +static const char *const cval[] = { +"WRM", "WMP", "WRR", "WPM", "WR0", "WR1", "WR2", "WR3", +"SBM", "RDM", "RDR", "ADM", "RD0", "RD1", "RD2", "RD3", +NULL }; + +static const char *const dval[] = { +"CLB", "CLC", "IAC", "CMC", "CMA", "RAL", "RAR", "TCC", +"DAC", "TCS", "STC", "DAA", "KBP", "DCL", +NULL }; + +static const char *const eval[] = { +"", "HLT", "BBS", "LCR", "OR4", "OR5", "AN6", "AN7", +"DB0", "DB1", "SB0", "SB1", "EIN", "DIN", "RPM", +NULL }; + +static const char *const fval[] = { +"0P", "1P", "2P", "3P", "4P", "5P", "6P", "7P", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval +}; + +static int match_i4004(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'f') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i4004(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 15)) { + eprint(_("argument (%d) must be in range [0-15]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'g': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 14 || (vs[i] & 1))) { + eprint( + _("argument (%d) must be an even number in range [0-14]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'h': b |= ((vs[i] >> 8) & 0x0f); + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'i': b |= (vs[i] << 4); break; + case 'j': b |= (vs[i] << 1); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i4004(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i4004(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'f') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i4004 = { + .id = "i4004", + .descr = "Intel 4004", + .matcht = s_matchtab_i4004, + .matchf = match_i4004, + .genf = gen_i4004, + .pat_char_rewind = pat_char_rewind_i4004, + .pat_next_str = pat_next_str_i4004, + .mask = 1 +}; + +const struct target s_target_i4040 = { + .id = "i4040", + .descr = "Intel 4040", + .matcht = s_matchtab_i4004, + .matchf = match_i4004, + .genf = gen_i4004, + .pat_char_rewind = pat_char_rewind_i4004, + .pat_next_str = pat_next_str_i4004, + .mask = 3 +}; + diff --git a/Tools/unix/uz80as/i8008.c b/Tools/unix/uz80as/i8008.c new file mode 100644 index 00000000..a7fd9f58 --- /dev/null +++ b/Tools/unix/uz80as/i8008.c @@ -0,0 +1,220 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8008. + * =========================================================================== + */ + +/* Max. memory 16K (14 bits addresses). */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ADA,ADB,ADC,ADD,ADH,ADL,ADM, + * ACA,ACB,ACC,ACD,ACH,ACL,ACM, + * SUA,SUB,SUC,SUD,SUH,SUL,SUM, + * SBA,SBB,SBC,SBD,SBH,SBL,SBM, + * NDA,NDB,NDC,NDD,NDH,NDL,NDM, + * XRA,XRB,XRC,XRD,XRH,XRL,XRM, + * ORA,ORB,ORC,ORD,ORH,ORL,ORM, + * CPA,CPB,CPC,CPD,CPH,CPL,CPM + * c: NOP,LAB,LAC,LAD,LAE,LAH,LAL,LAM, + * LBA,LBB,LBC,LBD,LBE,LBH,LBL,LBM, + * LCA,LCB,LCC,LCD,LCE,LCH,LCL,LCM, + * LDA,LDB,LDC,LDD,LDE,LDH,LDL,LDM, + * LEA,LEB,LEC,LED,LEE,LEH,LEL,LEM, + * LHA,LHB,LHC,LHD,LHE,LHH,LHL,LHM, + * LLA,LLB,LLC,LLD,LLE,LLH,LLL,LLM, + * LMA,LMB,LMC,LMD,LME,LMH,LML,HLT + * d: JFC,CFC,JMP,CAL,JFZ,CFZ, + * JFS,CFS,JFP,CFP, + * JTC,CTC,JTZ,CTZ, + * JTS,CTS,JTP,CTP + * e: INB,INC,IND,INE,INH,INL + * f: DCB,DCC,DCD,DCE,DCH,DCL + * g: ADI,ACI,SUI,SBI,NDI,XRI,ORI,CPI + * h: LAI,LBI,LCI,LDI,LEI,LHI,LLI,LMI + * i: RFC,RFS,RTC,RTS,RFZ,RFP,RTZ,RTP + * j: RLC,RRC,RAL,RAR + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 1) | lastbyte, op in [0-7] + * g: (op << 1) | lastbyte, op in [8-31] + * h: (op << 4) | lastbyte + * i: (op << 1) | lastbyte + * j: (op << 3) | lastbyte, op in [0-7] + */ + +static const struct matchtab s_matchtab_i8008[] = { + { "RET", "07.", 1, 0 }, + { "j", "02b0.", 1, 0 }, + { "i", "03b0.", 1, 0 }, + { "h a", "06b0.d1.", 1, 0, "e8" }, + { "g a", "04b0.d1.", 1, 0, "e8" }, + { "e", "00b0.", 1, 0 }, + { "f", "01b0.", 1, 0 }, + { "RST a", "05j0.", 1, 0, "b3" }, + { "d a", "40i0.e1", 1, 0 }, + { "INP a", "41f0.", 1, 0, "b3" }, + { "OUT a", "41g0.", 1, 0, "kk" }, + { "b", "80c0.", 1, 0 }, + { "c", "C0c0.", 1, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"ADA", "ADB", "ADC", "ADD", "ADE", "ADH", "ADL", "ADM", +"ACA", "ACB", "ACC", "ACD", "ACE", "ACH", "ACL", "ACM", +"SUA", "SUB", "SUC", "SUD", "SUE", "SUH", "SUL", "SUM", +"SBA", "SBB", "SBC", "SBD", "SBE", "SBH", "SBL", "SBM", +"NDA", "NDB", "NDC", "NDD", "NDE", "NDH", "NDL", "NDM", +"XRA", "XRB", "XRC", "XRD", "XRE", "XRH", "XRL", "XRM", +"ORA", "ORB", "ORC", "ORD", "ORE", "ORH", "ORL", "ORM", +"CPA", "CPB", "CPC", "CPD", "CPE", "CPH", "CPL", "CPM", +NULL }; + +static const char *const cval[] = { +"NOP", "LAB", "LAC", "LAD", "LAE", "LAH", "LAL", "LAM", +"LBA", "LBB", "LBC", "LBD", "LBE", "LBH", "LBL", "LBM", +"LCA", "LCB", "LCC", "LCD", "LCE", "LCH", "LCL", "LCM", +"LDA", "LDB", "LDC", "LDD", "LDE", "LDH", "LDL", "LDM", +"LEA", "LEB", "LEC", "LED", "LEE", "LEH", "LEL", "LEM", +"LHA", "LHB", "LHC", "LHD", "LHE", "LHH", "LHL", "LHM", +"LLA", "LLB", "LLC", "LLD", "LLE", "LLH", "LLL", "LLM", +"LMA", "LMB", "LMC", "LMD", "LME", "LMH", "LML", "HLT", +NULL }; + +static const char *const dval[] = { +"JFC", "CFC", "JMP", "CAL", "JFZ", "CFZ", "", "", +"JFS", "CFS", "", "", "JFP", "CFP", "", "", +"JTC", "CTC", "", "", "JTZ", "CTZ", "", "", +"JTS", "CTS", "", "", "JTP", "CTP", +NULL }; + +static const char *const eval[] = { "", "INB", "INC", "IND", + "INE", "INH", "INL", + NULL }; + +static const char *const fval[] = { "", "DCB", "DCC", "DCD", + "DCE", "DCH", "DCL", + NULL }; + +static const char *const gval[] = { "ADI", "ACI", "SUI", "SBI", + "NDI", "XRI", "ORI", "CPI", + NULL }; + +static const char *const hval[] = { "LAI", "LBI", "LCI", "LDI", + "LEI", "LHI", "LLI", "LMI", + NULL }; + +static const char *const ival[] = { "RFC", "RFZ", "RFS", "RFP", + "RTC", "RTZ", "RTS", "RTP", + NULL }; + +static const char *const jval[] = { "RLC", "RRC", "RAL", "RAR", + NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival, jval +}; + +static int match_i8008(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'j') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8008(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 7)) { + eprint(_("argument (%d) must be in range [0-7]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 1); + break; + case 'g': if (s_pass > 0 && (vs[i] < 8 || vs[i] > 31)) { + eprint(_("argument (%d) must be in range [8-31]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 1); + break; + case 'h': b |= (vs[i] << 4); break; + case 'i': b |= (vs[i] << 1); break; + case 'j': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 7)) { + eprint(_("argument (%d) must be in range [0-7]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 3); + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8008(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8008(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'j') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8008 = { + .id = "i8008", + .descr = "Intel 8008", + .matcht = s_matchtab_i8008, + .matchf = match_i8008, + .genf = gen_i8008, + .pat_char_rewind = pat_char_rewind_i8008, + .pat_next_str = pat_next_str_i8008, + .mask = 1 +}; + diff --git a/Tools/unix/uz80as/i8048.c b/Tools/unix/uz80as/i8048.c new file mode 100644 index 00000000..1526d252 --- /dev/null +++ b/Tools/unix/uz80as/i8048.c @@ -0,0 +1,276 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8021. + * Intel 8022. + * Intel 8041. + * Intel 8048. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: R0,R1,R2,R3,R4,R5,R6,R7 + * c: R0,R1 + * d: P1,P2 + * e: P4,P5,P6,P7 + * f: JB0,JB1,JB2,JB3,JB4,JB5,JB6,JB7 + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: output lastbyte | ((op & 0x700) >> 3) + * output op as 8 bit value + * (no '.' should follow) + * g: (op << 5) | lastbyte + */ + +static const struct matchtab s_matchtab_i8048[] = { + { "NOP", "00.", 1, 0 }, + { "ADD A,b", "68c0.", 1, 0 }, + { "ADD A,@c", "60c0.", 1, 0 }, + { "ADD A,#a", "03.d0.", 1, 0, "e8" }, + { "ADDC A,b", "78c0.", 1, 0 }, + { "ADDC A,@c", "70c0.", 1, 0 }, + { "ADDC A,#a", "13.d0.", 1, 0, "e8" }, + { "ANL A,b", "58c0.", 1, 0 }, + { "ANL A,@c", "50c0.", 1, 0 }, + { "ANL A,#a", "53.d0.", 1, 0, "e8" }, + { "ANL BUS,#a", "98.d0.", 32, 0, "e8" }, + { "ANL d,#a", "98c0.d1.", 4, 0, "e8" }, + { "ANLD e,A", "9Cc0.", 1, 0 }, + { "CALL a", "14f0", 1, 0 }, + { "CLR A", "27.", 1, 0 }, + { "CLR C", "97.", 1, 0 }, + { "CLR F1", "A5.", 4, 0 }, + { "CLR F0", "85.", 4, 0 }, + { "CPL A", "37.", 1, 0 }, + { "CPL C", "A7.", 1, 0 }, + { "CPL F0", "95.", 4, 0 }, + { "CPL F1", "B5.", 4, 0 }, + { "DA A", "57.", 1, 0 }, + { "DEC A", "07.", 1, 0 }, + { "DEC b", "C8c0.", 4, 0 }, + { "DIS I", "15.", 2, 0 }, + { "DIS TCNTI", "35.", 2, 0 }, + { "DJNZ b,a", "E8c0.d1.", 1, 0, "e8" }, + { "EN DMA", "E5.", 64, 0 }, + { "EN FLAGS", "F5.", 64, 0 }, + { "EN I", "05.", 2, 0 }, + { "EN TCNTI", "25.", 2, 0 }, + { "ENT0 CLK", "75.", 32, 0 }, + { "IN A,DBB", "22.", 64, 0 }, + { "IN A,P0", "08.", 8, 0 }, + { "IN A,d", "08c0.", 1, 0 }, + { "INC A", "17.", 1, 0 }, + { "INC b", "18c0.", 1, 0 }, + { "INC @c", "10c0.", 1, 0 }, + { "INS A,BUS", "08.", 32, 0 }, + { "f a", "12g0.d1.", 4, 0, "e8" }, + { "JC a", "F6.d0.", 1, 0, "e8" }, + { "JF0 a", "B6.d0.", 4, 0, "e8" }, + { "JF1 a", "76.d0.", 4, 0, "e8" }, + { "JMP a", "04f0", 1, 0, "e11" }, + { "JMPP @A", "B3.", 1, 0 }, + { "JNC a", "E6.d0.", 1, 0, "e8" }, + { "JNI a", "86.d0.", 32, 0, "e8" }, + { "JNIBF a", "D6.d0.", 64, 0, "e8" }, + { "JNT0 a", "26.d0.", 2, 0, "e8" }, + { "JNT1 a", "46.d0.", 1, 0, "e8" }, + { "JNZ a", "96.d0.", 1, 0, "e8" }, + { "JOBF a", "86.d0.", 64, 0, "e8" }, + { "JTF a", "16.d0.", 1, 0, "e8" }, + { "JT0 a", "36.d0.", 2, 0, "e8" }, + { "JT1 a", "56.d0.", 1, 0, "e8" }, + { "JZ a", "C6.d0.", 1, 0, "e8" }, + { "MOV A,#a", "23.d0.", 1, 0, "e8" }, + { "MOV A,PSW", "C7.", 4, 0 }, + { "MOV A,b", "F8c0.", 1, 0 }, + { "MOV A,@c", "F0c0.", 1, 0 }, + { "MOV A,T", "42.", 1, 0 }, + { "MOV PSW,A", "D7.", 4, 0 }, + { "MOV b,A", "A8c0.", 1, 0 }, + { "MOV b,#a", "B8c0.d1.", 1, 0, "e8" }, + { "MOV @c,A", "A0c0.", 1, 0 }, + { "MOV @c,#a", "B0c0.d1.", 1, 0, "e8" }, + { "MOV STS,A", "90.", 64, 0 }, + { "MOV T,A", "62.", 1, 0 }, + { "MOVD A,e", "0Cc0.", 1, 0 }, + { "MOVD e,A", "3Cc0.", 1, 0 }, + { "MOVP A,@A", "A3.", 1, 0 }, + { "MOVP3 A,@A", "E3.", 4, 0 }, + { "MOVX A,@c", "80c0.", 32, 0 }, + { "MOVX @c,A", "90c0.", 32, 0 }, + { "NOP", "00.", 1, 0 }, + { "ORL A,b", "48c0.", 1, 0 }, + { "ORL A,@c", "40c0.", 1, 0 }, + { "ORL A,#a", "43.d0.", 1, 0, "e8" }, + { "ORL BUS,#a", "88.d0.", 32, 0, "e8" }, + { "ORL d,#a", "88c0.d1.", 4, 0, "e8" }, + { "ORLD e,A", "8Cc0.", 1, 0 }, + { "OUT DBB,A", "02.", 64, 0 }, + { "OUTL BUS,A", "02.", 32, 0 }, + { "OUTL P0,A", "90.", 8, 0 }, + { "OUTL d,A", "38c0.", 1, 0 }, + { "RAD", "80.", 16, 0 }, + { "RET", "83.", 1, 0 }, + { "RETR", "93.", 4, 0 }, + { "RETI", "93.", 16, 0 }, + { "RL A", "E7.", 1, 0 }, + { "RLC A", "F7.", 1, 0 }, + { "RR A", "77.", 1, 0 }, + { "RRC A", "67.", 1, 0 }, + { "SEL AN0", "85.", 16, 0 }, + { "SEL AN1", "95.", 16, 0 }, + { "SEL MB0", "E5.", 32, 0 }, + { "SEL MB1", "F5.", 32, 0 }, + { "SEL RB0", "C5.", 4, 0 }, + { "SEL RB1", "D5.", 4, 0 }, + { "STOP TCNT", "65.", 1, 0 }, + { "STRT CNT", "45.", 1, 0 }, + { "STRT T", "55.", 1, 0 }, + { "SWAP A", "47.", 1, 0 }, + { "XCH A,b", "28c0.", 1, 0 }, + { "XCH A,@c", "20c0.", 1, 0 }, + { "XCHD A,@c", "30c0.", 1, 0 }, + { "XRL A,b", "D8c0.", 1, 0 }, + { "XRL A,@c", "D0c0.", 1, 0 }, + { "XRL A,#a", "D3.d0.", 1, 0, "e8" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", +NULL }; + +static const char *const cval[] = { +"R0", "R1", +NULL }; + +static const char *const dval[] = { +"", "P1", "P2", +NULL }; + +static const char *const eval[] = { +"P4", "P5", "P6", "P7", +NULL }; + +static const char *const fval[] = { +"JB0", "JB1", "JB2", "JB3", "JB4", "JB5", "JB6", "JB7", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval +}; + +static int match_i8048(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'f') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8048(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= ((vs[i] & 0x700) >> 3); + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'g': b |= (vs[i] << 5); + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8048(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8048(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'f') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8041 = { + .id = "i8041", + .descr = "Intel 8041", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 71 +}; + +const struct target s_target_i8048 = { + .id = "i8048", + .descr = "Intel 8048", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 39 +}; + +const struct target s_target_i8021 = { + .id = "i8021", + .descr = "Intel 8021", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 9 +}; + +const struct target s_target_i8022 = { + .id = "i8022", + .descr = "Intel 8022", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 27 +}; + diff --git a/Tools/unix/uz80as/i8051.c b/Tools/unix/uz80as/i8051.c new file mode 100644 index 00000000..034e1292 --- /dev/null +++ b/Tools/unix/uz80as/i8051.c @@ -0,0 +1,244 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8051. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: R0,R1,R2,R3,R4,R5,R6,R7 + * c: R0,R1 + * d: ADD,ADDC,ORL,ANL,XRL,SUBB,XCH,MOV + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: output lastbyte | ((op & 0x700) >> 3) + * output op as 8 bit value + * (no '.' should follow) + * g: relative jump -2 + * h: relative jump -3 + * i: ouput op as big endian word + * j: b + (op << 4) + */ + +static const struct matchtab s_matchtab_i8051[] = { + { "ACALL a", "11f0", 1, 0, "e11" }, + { "d A,b", "08j0c1.", 1, 0 }, + { "d A,@c", "06j0c1.", 1, 0 }, + { "ADD A,#a", "24.d0.", 1, 0, "e8" }, + { "ADDC A,#a", "34.d0.", 1, 0, "e8" }, + { "ORL A,#a", "44.d0.", 1, 0, "e8" }, + { "ANL A,#a", "54.d0.", 1, 0, "e8" }, + { "XRL A,#a", "64.d0.", 1, 0, "e8" }, + { "SUBB A,#a", "94.d0.", 1, 0, "e8" }, + { "MOV A,#a", "74.d0.", 1, 0, "e8" }, + { "d A,a", "05j0.d1.", 1, 0, "e8" }, + // { "ADD A,b", "28c0.", 1, 0 }, + // { "ADD A,@c", "26c0.", 1, 0 }, + // { "ADD A,a", "25.d0.", 1, 0, "e8" }, + // { "ADDC A,b", "38c0.", 1, 0 }, + // { "ADDC A,@c", "36c0.", 1, 0 }, + // { "ADDC A,a", "35.d0.", 1, 0, "e8" }, + { "AJMP a", "01f0", 1, 0, "e11" }, + // { "ANL A,b", "58c0.", 1, 0 }, + // { "ANL A,@c", "56c0.", 1, 0 }, + // { "ANL A,a", "55.d0.", 1, 0, "e8" }, + { "ANL C,/a", "B0.d0.", 1, 0, "e8" }, + { "ANL C,a", "82.d0.", 1, 0, "e8" }, + { "ANL a,A", "52.d0.", 1, 0 }, + { "ANL a,#a", "53.d0.d1.", 1, 0, "e8e8" }, + { "CJNE A,#a,a", "B4.d0.h1.", 1, 0, "e8r8" }, + { "CJNE b,#a,a", "B8c0.d1.h2.", 1, 0, "e8r8" }, + { "CJNE @c,#a,a", "B6c0.d1.h2.", 1, 0, "e8r8" }, + { "CJNE A,a,a", "B5.d0.h1.", 1, 0, "e8r8" }, + { "CLR A", "E4.", 1, 0 }, + { "CLR C", "C3.", 1, 0 }, + { "CLR a", "C2.d0.", 1, 0, "e8" }, + { "CPL A", "F4.", 1, 0 }, + { "CPL C", "B3.", 1, 0 }, + { "CPL a", "B2.d0.", 1, 0, "e8" }, + { "DA A", "D4.", 1, 0 }, + { "DEC A", "14.", 1, 0 }, + { "DEC b", "18c0.", 1, 0 }, + { "DEC @c", "16c0.", 1, 0 }, + { "DEC a", "15.d0.", 1, 0, "e8" }, + { "DIV AB", "84.", 1, 0 }, + { "DJNZ b,a", "D8c0.g1.", 1, 0, "r8" }, + { "DJNZ a,a", "D5.d0.h1.", 1, 0, "e8r8" }, + { "INC DPTR", "A3.", 1, 0 }, + { "INC A", "04.", 1, 0 }, + { "INC b", "08c0.", 1, 0 }, + { "INC @c", "06c0.", 1, 0 }, + { "INC a", "05.d0.", 1, 0, "e8" }, + { "JB a,a", "20.d0.h1.", 1, 0, "e8r8" }, + { "JBC a,a", "10.d0.h1.", 1, 0, "e8r8" }, + { "JC a", "40.g0.", 1, 0, "r8" }, + { "JMP @A+DPTR", "73.", 1, 0 }, + { "JNB a,a", "30.d0.h1.", 1, 0, "e8r8" }, + { "JNC a", "50.g0.", 1, 0, "r8" }, + { "JNZ a", "70.g0.", 1, 0, "r8" }, + { "JZ a", "60.g0.", 1, 0, "r8" }, + { "LCALL a", "12.i0", 1, 0 }, + { "LJMP a", "02.i0", 1, 0 }, + // { "MOV A,b", "E8c0.", 1, 0 }, + // { "MOV A,@c", "E6c0.", 1, 0 }, + // { "MOV A,a", "E5.d0.", 1, 0, "e8" }, // MOV A,ACC not valid? + { "MOV b,A", "F8c0.", 1, 0 }, + { "MOV b,#a", "78c0.d1.", 1, 0, "e8" }, + { "MOV b,a", "A8c0.d1.", 1, 0, "e8" }, + { "MOV @c,A", "F6c0.", 1, 0 }, + { "MOV @c,#a", "76c0.d1.", 1, 0, "e8" }, + { "MOV @c,a", "A6c0.d1.", 1, 0, "e8" }, + { "MOV C,a", "A2.d0.", 1, 0, "e8" }, + { "MOV DPTR,#a", "90.i0", 1, 0, "e8" }, + { "MOV a,A", "F5.d0.", 1, 0, "e8" }, + { "MOV a,C", "92.d0.", 1, 0, "e8" }, + { "MOV a,b", "88c1.d0.", 1, 0, "e8" }, + { "MOV a,@c", "86c1.d0.", 1, 0, "e8" }, + { "MOV a,#a", "75.d0.d1.", 1, 0, "e8e8" }, + { "MOV a,a", "85.d1.d0.", 1, 0, "e8e8" }, + { "MOVC A,@A+DPTR", "93.", 1, 0 }, + { "MOVC A,@A+PC", "83.", 1, 0 }, + { "MOVX A,@c", "E2c0.", 1, 0 }, + { "MOVX A,@DPTR", "E0.", 1, 0 }, + { "MOVX @c,A", "F2c0.", 1, 0 }, + { "MOVX @DPTR,A", "F0.", 1, 0 }, + { "MUL AB", "A4.", 1, 0 }, + { "NOP", "00.", 1, 0 }, + // { "ORL A,b", "48c0.", 1, 0 }, + // { "ORL A,@c", "46c0.", 1, 0 }, + // { "ORL A,a", "45.d0.", 1, 0, "e8" }, + { "ORL C,/a", "A0.d0.", 1, 0, "e8" }, + { "ORL C,a", "72.d0.", 1, 0, "e8" }, + { "ORL a,A", "42.d0.", 1, 0, "e8" }, + { "ORL a,#a", "43.d0.d1.", 1, 0, "e8e8" }, + { "POP a", "D0.d0.", 1, 0, "e8" }, + { "PUSH a", "C0.d0.", 1, 0, "e8" }, + { "RET", "22.", 1, 0 }, + { "RETI", "32.", 1, 0 }, + { "RL A", "23.", 1, 0 }, + { "RLC A", "33.", 1, 0 }, + { "RR A", "03.", 1, 0 }, + { "RRC A", "13.", 1, 0 }, + { "SETB C", "D3.", 1, 0 }, + { "SETB a", "D2.d0.", 1, 0, "e8" }, + { "SJMP a", "80.g0.", 1, 0, "r8" }, + // { "SUBB A,b", "98c0.", 1, 0 }, + // { "SUBB A,@c", "96c0.", 1, 0 }, + // { "SUBB A,a", "95.d0.", 1, 0, "e8" }, + { "SWAP A", "C4.", 1, 0 }, + // { "XCH A,b", "C8c0.", 1, 0 }, + // { "XCH A,@c", "C6c0.", 1, 0 }, + // { "XCH A,a", "C5.d0.", 1, 0, "e8" }, + { "XCHD A,@c", "D6c0.", 1, 0 }, + // { "XRL A,b", "68c0.", 1, 0 }, + // { "XRL A,@c", "66c0.", 1, 0 }, + // { "XRL A,a", "65.d0.", 1, 0, "e8" }, + { "XRL a,A", "62.d0.", 1, 0, "e8" }, + { "XRL a,#a", "63.d0.d1.", 1, 0, "e8e8" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", +NULL }; + +static const char *const cval[] = { +"R0", "R1", +NULL }; + +static const char *const dval[] = { +"", "", "ADD", "ADDC", "ORL", "ANL", "XRL", "", +"", "SUBB", "", "", "XCH", "", "MOV", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval +}; + +static int match_i8051(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'd') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8051(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= ((vs[i] & 0x700) >> 3); + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'g': b = (vs[i] - savepc - 2); break; + break; + case 'h': b = (vs[i] - savepc - 3); break; + break; + case 'i': genb(vs[i] >> 8, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'j': b += (vs[i] << 4); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8051(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8051(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'd') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8051 = { + .id = "i8051", + .descr = "Intel 8051", + .matcht = s_matchtab_i8051, + .matchf = match_i8051, + .genf = gen_i8051, + .pat_char_rewind = pat_char_rewind_i8051, + .pat_next_str = pat_next_str_i8051, + .mask = 1 +}; + diff --git a/Tools/unix/uz80as/i8080.c b/Tools/unix/uz80as/i8080.c new file mode 100644 index 00000000..e0f7f51b --- /dev/null +++ b/Tools/unix/uz80as/i8080.c @@ -0,0 +1,214 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8080. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: B,C,D,E,H,L,M,A + * c: B,D,H,SP + * d: B,D + * e: B,D,H,PSW + * f: JNZ,JZ,JNC,JC,JPO,JPE,JP,JM + * g: CNZ,CZ,CNC,CC,CPO,CPE,CP,CM + * h: RNZ,RZ,RNC,RC,RPO,RPE,RP,RM + * i: B,C,D,E,H,L,A + * j: ADD,ADC,SUB,SBB,ANA,XRA,ORA,CMP + * k: RLC,RRC,RAL,RAR + * l: ADI,ACI,SUI,SBI,ANI,XRI,ORI,CPI + * m: SHLD,LHLD,STA,LDA + * n: DI,EI + * o: OUT,IN + * p: STC,CMC + * q: POP,PUSH + * r: STAX,LDAX + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 4) | lastbyte + * g: possible value to RST + * h: (op << 2) | lastbyte + */ + +static const struct matchtab s_matchtab_i8080[] = { + { "MOV M,i", "70c0.", 3, 0 }, + { "MOV i,M", "46b0.", 3, 0 }, + { "MOV i,i", "40b0c1.", 3, 0 }, + { "MVI b,a", "06b0.d1.", 3, 0, "e8" }, + { "LXI c,a", "01f0.e1", 3, 0 }, + { "m a", "22b0.e1", 3, 0 }, + { "r d", "02b0f1.", 3, 0 }, + { "XCHG", "EB.", 3, 0 }, + { "j b", "80b0c1.", 3, 0 }, + { "l a", "C6b0.d1.", 3, 0, "e8" }, + { "INR b", "04b0.", 3, 0 }, + { "DCR b", "05b0.", 3, 0 }, + { "INX c", "03f0.", 3, 0 }, + { "DCX c", "0Bf0.", 3, 0 }, + { "DAD c", "09f0.", 3, 0 }, + { "DAA", "27.", 3, 0 }, + { "k", "07b0.", 3, 0 }, + { "CMA", "2F.", 3, 0 }, + { "p", "37b0.", 3, 0 }, + { "JMP a", "C3.e0", 3, 0 }, + { "f a", "C2b0.e1", 3, 0 }, + { "CALL a", "CD.e0", 3, 0 }, + { "g a", "C4b0.e1", 3, 0 }, + { "RET", "C9.", 3, 0 }, + { "h", "C0b0.", 3, 0 }, + { "RST a", "C7g0.", 3, 0, "b3" }, + { "PCHL", "E9.", 3, 0 }, + { "q e", "C1h0f1.", 3, 0 }, + { "XTHL", "E3.", 3, 0 }, + { "SPHL", "F9.", 3, 0 }, + { "o a", "D3b0.d1.", 3, 0, "e8" }, + { "n", "F3b0.", 3, 0 }, + { "HLT", "76.", 3, 0 }, + { "NOP", "00.", 3, 0 }, + /* 8085 added instructions */ + { "RIM", "20.", 2, 0 }, + { "SIM", "30.", 2, 0 }, + { "ARHL", "10.", 2, 2 }, + { "DSUB", "08.", 2, 2 }, + { "RDEL", "18.", 2, 2 }, + { "LDHI a", "28.d0.", 2, 2, "e8" }, + { "LDSI a", "38.d0.", 2, 2, "e8" }, + { "RSTV", "CB.", 2, 2 }, + { "SHLX", "D9.", 2, 2 }, + { "LHLX", "ED.", 2, 2 }, + { "JNK a", "DD.e0", 2, 2 }, + { "JNX5 a", "DD.e0", 2, 2 }, + { "JNUI a", "DD.e0", 2, 2 }, + { "JK a", "FD.e0", 2, 2 }, + { "JX5 a", "FD.e0", 2, 2 }, + { "JUI a", "FD.e0", 2, 2 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { "B", "C", "D", "E", + "H", "L", "M", "A", NULL }; +static const char *const cval[] = { "B", "D", "H", "SP", NULL }; +static const char *const dval[] = { "B", "D", NULL }; +static const char *const eval[] = { "B", "D", "H", "PSW", NULL }; +static const char *const fval[] = { "JNZ", "JZ", "JNC", "JC", + "JPO", "JPE", "JP", "JM", NULL }; +static const char *const gval[] = { "CNZ", "CZ", "CNC", "CC", + "CPO", "CPE", "CP", "CM", NULL }; +static const char *const hval[] = { "RNZ", "RZ", "RNC", "RC", + "RPO", "RPE", "RP", "RM", NULL }; +static const char *const ival[] = { "B", "C", "D", "E", + "H", "L", "", "A", NULL }; +static const char *const jval[] = { "ADD", "ADC", "SUB", "SBB", + "ANA", "XRA", "ORA", "CMP", NULL }; +static const char *const kval[] = { "RLC", "RRC", "RAL", "RAR", NULL }; +static const char *const lval[] = { "ADI", "ACI", "SUI", "SBI", + "ANI", "XRI", "ORI", "CPI", NULL }; +static const char *const mval[] = { "SHLD", "LHLD", "STA", "LDA", NULL }; +static const char *const nval[] = { "DI", "EI", NULL }; +static const char *const oval[] = { "OUT", "IN", NULL }; +static const char *const pval[] = { "STC", "CMC", NULL }; +static const char *const qval[] = { "POP", "PUSH", NULL }; +static const char *const rval[] = { "STAX", "LDAX", NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival, jval, kval, + lval, mval, nval, oval, pval, + qval, rval +}; + +static int match_i8080(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'r') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8080(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 4); break; + case 'g': if (s_pass > 0 && (vs[i] & ~7) != 0) { + eprint(_("invalid RST argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 3); + break; + case 'h': b |= (vs[i] << 2); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8080(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8080(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'r') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8080 = { + .id = "i8080", + .descr = "Intel 8080", + .matcht = s_matchtab_i8080, + .matchf = match_i8080, + .genf = gen_i8080, + .pat_char_rewind = pat_char_rewind_i8080, + .pat_next_str = pat_next_str_i8080, + .mask = 1 +}; + +const struct target s_target_i8085 = { + .id = "i8085", + .descr = "Intel 8085", + .matcht = s_matchtab_i8080, + .matchf = match_i8080, + .genf = gen_i8080, + .pat_char_rewind = pat_char_rewind_i8080, + .pat_next_str = pat_next_str_i8080, + .mask = 2 +}; diff --git a/Tools/unix/uz80as/incl.c b/Tools/unix/uz80as/incl.c new file mode 100644 index 00000000..e11d4675 --- /dev/null +++ b/Tools/unix/uz80as/incl.c @@ -0,0 +1,81 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Include file stack. + * =========================================================================== + */ + +#include "config.h" +#include "incl.h" +#include "utils.h" +#include "err.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +/* Max number of nested included files. */ +#define NFILES 128 + +/* Number of nested files. */ +static int s_nfiles; + +/* Current file. */ +static struct incfile *s_curfile; + +/* Get the current file. Never returns NULL. */ +struct incfile *curfile(void) +{ + assert(s_curfile != NULL); + return s_curfile; +} + +/* The number of nested files. 0 means no file loaded. */ +int nfiles(void) +{ + return s_nfiles; +} + +/* Leave the current included file. */ +void popfile(void) +{ + struct incfile *ifile; + + assert(s_curfile != NULL); + fclose(s_curfile->fin); + ifile = s_curfile; + s_curfile = ifile->prev; + free(ifile); + s_nfiles--; +} + +/* Include a file whose name is [p, q[. */ +void pushfile(const char *p, const char *q) +{ + struct incfile *ifile; + + if (s_nfiles == NFILES) { + eprint(_("maximum number of nested includes exceeded (%d)\n"), + NFILES); + exit(EXIT_FAILURE); + } + + // printf("pushfile: %s\n", p); + ifile = emalloc((sizeof *ifile) + (q - p) + 1); + ifile->name = (char *) ((unsigned char *) ifile + sizeof *ifile); + copychars(ifile->name, p, q); + + ifile->fin = efopen(ifile->name, "r"); + ifile->linenum = 0; + ifile->prev = s_curfile; + s_curfile = ifile; + s_nfiles++; +} diff --git a/Tools/unix/uz80as/incl.h b/Tools/unix/uz80as/incl.h new file mode 100644 index 00000000..1da62f61 --- /dev/null +++ b/Tools/unix/uz80as/incl.h @@ -0,0 +1,28 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Include file stack. + * =========================================================================== + */ + +#ifndef INCL_H +#define INCL_H + +#ifndef STDIO_H +#define STDIO_H +#include +#endif + +struct incfile { + struct incfile *prev; + FILE *fin; + int linenum; + char *name; +}; + +void pushfile(const char *p, const char *q); +void popfile(void); +struct incfile *curfile(void); +int nfiles(void); + +#endif diff --git a/Tools/unix/uz80as/list.c b/Tools/unix/uz80as/list.c new file mode 100644 index 00000000..f340e30b --- /dev/null +++ b/Tools/unix/uz80as/list.c @@ -0,0 +1,164 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembly listing generation. + * =========================================================================== + */ + +#include "config.h" +#include "list.h" +#include "err.h" + +#ifndef STDIO_H +#include +#endif + +#ifndef STLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +static FILE *s_list_file; + +/* N characters in current line. */ +static int s_nchars; + +/* Generated data bytes in this line. */ +static int s_nbytes; + +/* Line text. */ +static const char *s_line; + +/* Line number. */ +static int s_linenum; + +/* Program counter. */ +static int s_pc; + +/* Number of current nested files. */ +static int s_nfiles; + +/* If listing line number, etc are generated or just the line. */ +int s_codes = 1; + +/* If listing is enabled or not. */ +int s_list_on = 1; + +/* If we are skipping lines. */ +static int s_skip_on = 0; + +void list_open(const char *fname) +{ + s_list_file = fopen(fname, "w"); + if (s_list_file == NULL) { + eprint(_("cannot open file %s\n"), fname); + } +} + +void list_close(void) +{ + if (s_list_file != NULL) + fclose(s_list_file); +} + +static void prhead(void) +{ + int i, j, n; + + s_nchars = fprintf(s_list_file, "%-.4d", s_linenum); + + n = 7 - s_nchars; + if (n <= 0) + n = 1; + j = 0; + if (s_nfiles > 0) + j = s_nfiles - 1; + if (j > n) + j = n; + for (i = 0; i < j; i++) + fputc('+', s_list_file); + j = n - j; + while (j--) + fputc(' ', s_list_file); + s_nchars += n; + + s_nchars += fprintf(s_list_file, "%.4X", s_pc); + if (s_skip_on) + fputc('~', s_list_file); + else + fputc(' ', s_list_file); + s_nchars += 1; +} + +static void prline(void) +{ + if (s_line == NULL) { + fputs("\n", s_list_file); + } else { + if (s_codes) { + while (s_nchars < 24) { + s_nchars++; + fputc(' ', s_list_file); + } + } + fprintf(s_list_file, "%s\n", s_line); + s_line = NULL; + } + s_nchars = 0; + s_nbytes = 0; +} + +void list_startln(const char *line, int linenum, int pc, int nested_files) +{ + if (s_list_file == NULL) + return; + s_linenum = linenum; + s_pc = pc; + s_line = line; + s_nchars = 0; + s_nbytes = 0; + s_nfiles = nested_files; +} + +void list_setpc(int pc) +{ + s_pc = pc; +} + +void list_skip(int on) +{ + s_skip_on = on; +} + +void list_eject(void) +{ + if (s_list_file == NULL || !s_list_on) + return; +} + +void list_genb(int b) +{ + if (s_list_file == NULL || !s_codes || !s_list_on) + return; + if (s_nchars == 0) + prhead(); + if (s_nbytes >= 4) { + prline(); + prhead(); + } + s_nchars += fprintf(s_list_file, "%2.2X ", (b & 0xff)); + s_nbytes++; + s_pc++; +} + +void list_endln(void) +{ + if (s_list_file == NULL || !s_list_on) + return; + if (s_codes && s_nchars == 0) + prhead(); + prline(); +} diff --git a/Tools/unix/uz80as/list.h b/Tools/unix/uz80as/list.h new file mode 100644 index 00000000..7868cd29 --- /dev/null +++ b/Tools/unix/uz80as/list.h @@ -0,0 +1,23 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembly listing generation. + * =========================================================================== + */ + +#ifndef LIST_H +#define LIST_H + +extern int s_codes; +extern int s_list_on; + +void list_open(const char *fname); +void list_close(void); +void list_startln(const char *line, int linenum, int pc, int nested_files); +void list_setpc(int pc); +void list_skip(int on); +void list_eject(void); +void list_genb(int b); +void list_endln(void); + +#endif diff --git a/Tools/unix/uz80as/main.c b/Tools/unix/uz80as/main.c new file mode 100644 index 00000000..e3e3ff1e --- /dev/null +++ b/Tools/unix/uz80as/main.c @@ -0,0 +1,303 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * main(), handling of command line options. + * =========================================================================== + */ + +#include "config.h" +#include "ngetopt.h" +#include "options.h" +#include "utils.h" +#include "err.h" +#include "uz80as.h" +#include "prtable.h" +#include "targets.h" + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +void print_copyright(FILE *f) +{ + static const char *copyright = +"Copyright (C) " COPYRIGHT_YEARS " Jorge Giner Cordero.\n"; + + fputs(copyright, f); +} + +static void print_license(FILE *f) +{ + static const char *license[] = { +"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." + }; + + int i; + + for (i = 0; i < NELEMS(license); i++) { + fprintf(f, "%s\n", license[i]); + } +} + +static void print_version(FILE *f) +{ + const struct target *p; + + fputs(PACKAGE_STRING, f); + fputs("\n", f); + fputs("Targets:", f); + for (p = first_target(); p != NULL; p = next_target()) { + fprintf(f, " %s", p->id); + } + fputs("\n", f); +} + +static void print_help(const char *argv0) +{ + static const char *help = +"Usage: %s [OPTION]... ASM_FILE [OBJ_FILE [LST_FILE]]\n" +"\n" +"Assemble ASM_FILE into OBJ_FILE and generate the listing LST_FILE.\n" +"If not specified, OBJ_FILE is ASM_FILE with the extension changed to .obj.\n" +"If not specified, LST_FILE is ASM_FILE with the extension changed to .lst.\n" +"\n" +"Options:\n" +" -h, --help Display this help and exit.\n" +" -V, --verbose be chatty.\n" +" -v, --version Output version information and exit.\n" +" -l, --license Display the license text and exit.\n" +" -d, --define=MACRO Define a macro.\n" +" -f, --fill=n Fill memory with value n.\n" +" -q, --quiet Do not generate the listing file.\n" +" -x, --extended Enable extended instruction syntax.\n" +" -u, --undocumented Enable undocumented instructions.\n" +" -t, --target=NAME Select the target micro. z80 is the default.\n" +" -e, --list-targets List the targets supported.\n" +"\n" +"Examples:\n" +" " PACKAGE " p.asm Assemble p.asm into p.obj\n" +" " PACKAGE " p.asm p.bin Assemble p.asm into p.bin\n" +" " PACKAGE " -d\"MUL(a,b) (a*b)\" p.asm Define the macro MUL and assemble p.asm\n" +"\n" +"Report bugs to: <" PACKAGE_BUGREPORT ">.\n" +"Home page: <" PACKAGE_URL ">.\n"; + + printf(help, argv0); +} + +/* + * Get the filename part of fname (that is, for ../../fname.abc, get + * fname.abc). + * Then substitute the extension .abd by .ext or append .ext. + */ +static char *mkfname(const char *fname, const char *ext) +{ + size_t alen, elen; + const char *p, *q; + char *s; + + alen = strlen(fname); + elen = strlen(ext); + + /* Find start of filename in path string */ + p = fname + alen; + while (p > fname && *p != '/' && *p != '\\') + p--; + + if (*p == '/' || *p == '\\') + p++; + + /* Find the extension */ + q = fname + alen; + while (q > p && *q != '.') + q--; + + if (*q != '.') + q = fname + alen; + + s = emalloc((q - p) + 1 + elen + 1); + if (q > p) + memmove(s, p, (q - p)); + s[q - p] = '\0'; + strcat(s, "."); + strcat(s, ext); + return s; +} + +static void parse_fill_byte(const char *optarg) +{ + int hi, lo; + + if (strlen(optarg) != 2) + goto error; + + if ((hi = hexval(optarg[0])) < 0) + goto error; + if ((lo = hexval(optarg[1])) < 0) + goto error; + + s_mem_fillval = hi * 16 + lo; + return; + +error: eprogname(); + fprintf(stderr, _("invalid command line fill value (%s)\n"), optarg); + eprogname(); + fprintf(stderr, " "); + fprintf(stderr, _("Please, use two hexadecimal digits.\n")); + exit(EXIT_FAILURE); +} + +static void parse_target_id(const char *optarg) +{ + const struct target *p; + + p = find_target(optarg); + if (p == NULL) { + eprogname(); + fprintf(stderr, _("invalid target '%s'\n"), optarg); + exit(EXIT_FAILURE); + } else { + s_target_id = p->id; + } +} + +static void list_targets(FILE *f) +{ + const struct target *p; + + for (p = first_target(); p != NULL; p = next_target()) { + fprintf(f, "%-14s%s\n", p->id, p->descr); + } +} + +int main(int argc, char *argv[]) +{ + int c; + struct ngetopt ngo; + + static struct ngetopt_opt ops[] = { + { "define", 1, 'd' }, + { "extended", 0, 'x' }, + { "list-targets", 0, 'e' }, + { "fill", 1, 'f' }, + { "help", 0, 'h' }, + { "license", 0, 'l' }, + { "quiet", 0, 'q' }, + { "target", 1, 't' }, + { "undocumented", 0, 'u' }, + { "version", 0, 'v' }, + { "print-table", 1, 0 }, + { "print-delta", 1, 0 }, + { "verbose", 0, 'V' }, + { NULL, 0, 0 }, + }; + + ngetopt_init(&ngo, argc, argv, ops); + do { + c = ngetopt_next(&ngo); + switch (c) { + case 'V': + verbose++; + break; + case 'v': + print_version(stdout); + exit(EXIT_SUCCESS); + case 'h': + print_help(argv[0]); + exit(EXIT_SUCCESS); + case 'l': + print_copyright(stdout); + fputs("\n", stdout); + print_license(stdout); + exit(EXIT_SUCCESS); + case 't': + parse_target_id(ngo.optarg); + break; + case 'e': + list_targets(stdout); + exit(EXIT_SUCCESS); + break; + case 'd': + predefine(ngo.optarg); + break; + case 'f': + parse_fill_byte(ngo.optarg); + break; + case 'q': + s_listing = 0; + break; + case 'x': + s_extended_op = 1; + break; + case 'u': + s_undocumented_op = 1; + break; + case '?': + eprint(_("unrecognized option %s\n"), + ngo.optarg); + exit(EXIT_FAILURE); + case ':': + eprint(_("%s needs an argument\n"), + ngo.optarg); + exit(EXIT_FAILURE); + case ';': + eprint(_("%s does not allow for arguments\n"), + ngo.optarg); + exit(EXIT_FAILURE); + case 0: + if (strcmp(ngo.optstr, "print-table") == 0) { + print_table(stdout, ngo.optarg); + exit(EXIT_SUCCESS); + } + } + } while (c != -1); + + if (argc == ngo.optind) { + eprint(_("wrong number of arguments\n")); + exit(EXIT_FAILURE); + } + + s_asmfname = argv[ngo.optind]; + + if (argc - ngo.optind > 1) + s_objfname = argv[ngo.optind + 1]; + else + s_objfname = mkfname(s_asmfname, "obj"); + + if (argc - ngo.optind > 2) + s_lstfname = argv[ngo.optind + 2]; + else + s_lstfname = mkfname(s_asmfname, "lst"); + + uz80as(); + return 0; +} diff --git a/Tools/unix/uz80as/mc6800.c b/Tools/unix/uz80as/mc6800.c new file mode 100644 index 00000000..4ff80be5 --- /dev/null +++ b/Tools/unix/uz80as/mc6800.c @@ -0,0 +1,338 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Motorola 6800, 6801. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: NEGA,COMA,LSRA,RORA,ASRA, + * ROLA,DECA,INCA,TSTA,CLRA + * c: NEGB,COMB,LSRB,RORB,ASRB, + * ROLB,DECB,INCB,TSTB,CLRB + * d: NEG,COM,LSR,ROR,ASR, + * ROL,DEC,INC,TST,JMP,CLR + * e: SUBA,CMPA,SBCA,ANDA,BITA,LDAA, + * EORA,ADCA,ORAA,ADDA + * f: SUBB,CMPB,SBCB,ANDB,BITB,LDAB, + * EORB,ADCB,ORAB,ADDB + * g: INX,DEX,CLV,SEV,CLC,SEC,CLI,SEI + * h: BRA,BHI,BLS,BCC,BCS,BNE,BEQ, + * BVC,BVS,BPL,BMI,BGE,BLT,BGT,BLE + * i: TSX,INS,PULA,PULB,DES,TXS,PSHA, + * PSHB,RTS,RTI,WAI,SWI + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: ouput op as big endian word (no '.' should follow) + * g: if op<=$ff output lastbyte and output op as byte + * else output (lastbyte | 0x20) and output op as big endian word + * (no '.' should follow) + * h: relative - 2 + * i: relative - 4 + * i: relative - 5 + */ + +static const struct matchtab s_matchtab_mc6800[] = { + { "NOP", "01.", 1, 0 }, + { "TAP", "06.", 1, 0 }, + { "TPA", "07.", 1, 0 }, + { "g", "08c0.", 1, 0 }, + { "SBA", "10.", 1, 0 }, + { "CBA", "11.", 1, 0 }, + { "TAB", "16.", 1, 0 }, + { "TBA", "17.", 1, 0 }, + { "DAA", "19.", 1, 0 }, + { "ABA", "1B.", 1, 0 }, + { "i", "30c0.", 1, 0 }, + { "h a", "20c0.h1.", 1, 0, "r8" }, + { "b", "40c0.", 1, 0 }, + { "c", "50c0.", 1, 0 }, + { "d a,X", "60c0.d1.", 1, 0, "e8" }, + { "d a,Y", "18.60c0.d1.", 8, 0, "e8" }, + { "d a", "70c0.f1", 1, 0, "e16" }, + { "e #a", "80c0.d1.", 1, 0, "e8" }, + { "f #a", "C0c0.d1.", 1, 0, "e8" }, + { "e >a", "B0c0.f1", 1, 0, "e16" }, + { "f >a", "F0c0.f1", 1, 0, "e16" }, + { "e a,X", "A0c0.d1.", 1, 0, "e8" }, + { "f a,X", "E0c0.d1.", 1, 0, "e8" }, + { "e a,Y", "18.A0c0.d1.", 8, 0, "e8" }, + { "f a,Y", "18.E0c0.d1.", 8, 0, "e8" }, + { "e a", "90c0g1", 1, 0 }, + { "f a", "D0c0g1", 1, 0 }, + { "STAA >a", "B7.f0", 1, 0, "e16" }, + { "STAA a,X", "A7.d0.", 1, 0, "e8" }, + { "STAA a,Y", "18.A7.d0.", 8, 0, "e8" }, + { "STAA a", "97g0", 1, 0 }, + { "STAB >a", "F7.f0", 1, 0, "e16" }, + { "STAB a,X", "E7.d0.", 1, 0, "e8" }, + { "STAB a,Y", "18.E7.d0.", 8, 0, "e8" }, + { "STAB a", "D7g0", 1, 0 }, + { "CPX #a", "8C.f0", 1, 0, "e16" }, + { "CPX >a", "BC.f0", 1, 0, "e16" }, + { "CPX a,X", "AC.d0.", 1, 0, "e8" }, + { "CPX a,Y", "CD.AC.d0.", 8, 0, "e8" }, + { "CPX a", "9Cg0", 1, 0 }, + { "LDS #a", "8E.f0", 1, 0, "e16" }, + { "LDS >a", "BE.f0", 1, 0, "e16" }, + { "LDS a,X", "AE.d0.", 1, 0, "e8" }, + { "LDS a,Y", "18.AE.d0.", 8, 0, "e8" }, + { "LDS a", "9Eg0", 1, 0 }, + { "STS >a", "BF.f0", 1, 0, "e16" }, + { "STS a,X", "AF.d0.", 1, 0, "e8" }, + { "STS a,Y", "18.AF.d0.", 8, 0, "e8" }, + { "STS a", "9Fg0", 1, 0 }, + { "LDX #a", "CE.f0", 1, 0, "e16" }, + { "LDX >a", "FE.f0", 1, 0, "e16" }, + { "LDX a,X", "EE.d0.", 1, 0, "e8" }, + { "LDX a,Y", "CD.EE.d0.", 8, 0, "e8" }, + { "LDX a", "DEg0", 1, 0 }, + { "STX >a", "FF.f0", 1, 0, "e16" }, + { "STX a,X", "EF.d0.", 1, 0, "e8" }, + { "STX a,Y", "CD.EF.d0.", 8, 0, "e8" }, + { "STX a", "DFg0", 1, 0 }, + { "BSR a", "8D.h0.", 1, 0, "r8" }, + { "JSR >a", "BD.f0", 4, 0, "e16" }, + { "JSR a,X", "AD.d0.", 1, 0, "e8" }, + { "JSR a,Y", "18.AD.d0.", 8, 0, "e8" }, + { "JSR a", "BD.f0", 2, 0, "e16" }, + { "JSR a", "9Dg0", 4, 0 }, + { "ABX", "3A.", 4, 0 }, + { "ADDD #a", "C3.f0", 4, 0, "e16" }, + { "ADDD >a", "F3.f0", 4, 0, "e16" }, + { "ADDD a,X", "E3.d0.", 4, 0, "e8" }, + { "ADDD a,Y", "18.E3.d0.", 8, 0, "e8" }, + { "ADDD a", "D3g0", 4, 0 }, + { "ASLD", "05.", 4, 0 }, + { "LSLD", "05.", 4, 0 }, + { "BHS a", "24.h0.", 4, 0, "r8" }, + { "BLO a", "25.h0.", 4, 0, "r8" }, + { "BRN a", "21.h0.", 4, 0, "r8" }, + { "LDD #a", "CC.f0", 4, 0, "e16" }, + { "LDD >a", "FC.f0", 4, 0, "e16" }, + { "LDD a,X", "EC.d0.", 4, 0, "e8" }, + { "LDD a,Y", "18.EC.d0.", 8, 0, "e8" }, + { "LDD a", "DCg0", 4, 0 }, + { "LSL a,X", "68.d0.", 4, 0, "e8" }, + { "LSL a,Y", "18.68.d0.", 8, 0, "e8" }, + { "LSL a", "78.f0", 4, 0, "e16" }, + { "LSRD", "04.", 4, 0 }, + { "MUL", "3D.", 4, 0 }, + { "PSHX", "3C.", 4, 0 }, + { "PSHY", "18.3C.", 8, 0 }, + { "PULX", "38.", 4, 0 }, + { "PULY", "18.38.", 8, 0 }, + { "STD >a", "FD.f0", 4, 0, "e16" }, + { "STD a,X", "ED.d0.", 4, 0, "e8" }, + { "STD a,Y", "18.ED.d0.", 8, 0, "e8" }, + { "STD a", "DDg0", 4, 0 }, + { "SUBD #a", "83.f0", 4, 0, "e16" }, + { "SUBD >a", "B3.f0", 4, 0, "e16" }, + { "SUBD a,X", "A3.d0.", 4, 0, "e8" }, + { "SUBD a,Y", "18.A3.d0.", 8, 0, "e8" }, + { "SUBD a", "93g0", 4, 0 }, + { "TEST", "00.", 8, 0 }, + { "IDIV", "02.", 8, 0 }, + { "FDIV", "03.", 8, 0 }, + { "BRSET a,X,a,a", "1E.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BRSET a,Y,a,a", "18.1E.d0.d1.j2.", 8, 0, "e8e8r8" }, + { "BRSET a,a,a", "12.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BRCLR a,X,a,a", "1F.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BRCLR a,Y,a,a", "18.1F.d0.d1.j2.", 8, 0, "e8e8r8" }, + { "BRCLR a,a,a", "13.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BSET a,X,a", "1C.d0.d1.", 8, 0, "e8e8" }, + { "BSET a,Y,a", "18.1C.d0.d1.", 8, 0, "e8e8" }, + { "BSET a,a", "14.d0.d1.", 8, 0, "e8e8" }, + { "BCLR a,X,a", "1D.d0.d1.", 8, 0, "e8e8" }, + { "BCLR a,Y,a", "18.1D.d0.d1.", 8, 0, "e8e8" }, + { "BCLR a,a", "15.d0.d1.", 8, 0, "e8e8" }, + { "LSLA", "48.", 8, 0 }, + { "LSLB", "58.", 8, 0 }, + { "XGDX", "8F.", 8, 0 }, + { "STOP", "CF.", 8, 0 }, + { "ABY", "18.3A.", 8, 0 }, + { "CPY #a", "18.8C.f0", 8, 0, "e16" }, + { "CPY >a", "18.BC.f0", 8, 0, "e16" }, + { "CPY a,X", "1A.AC.d0.", 8, 0, "e8" }, + { "CPY a,Y", "18.AC.d0.", 8, 0, "e8" }, + { "CPY a", "18.9Cg0", 8, 0 }, + { "DEY", "18.09.", 8, 0 }, + { "INY", "18.08.", 8, 0 }, + { "LDY #a", "18.CE.f0", 8, 0, "e16" }, + { "LDY >a", "18.FE.f0", 8, 0, "e16" }, + { "LDY a,X", "1A.EE.d0.", 8, 0, "e8" }, + { "LDY a,Y", "18.EE.d0.", 8, 0, "e8" }, + { "LDY a", "18.DEg0", 8, 0 }, + { "STY >a", "18.FF.f0", 8, 0, "e16" }, + { "STY a,X", "1A.EF.d0.", 8, 0, "e8" }, + { "STY a,Y", "18.EF.d0.", 8, 0, "e8" }, + { "STY a", "18.DFg0", 8, 0 }, + { "TSY", "18.30.", 8, 0 }, + { "TYS", "18.35.", 8, 0 }, + { "XGDY", "18.8F.", 8, 0 }, + { "CPD #a", "1A.83.f0", 8, 0, "e16" }, + { "CPD >a", "1A.B3.f0", 8, 0, "e16" }, + { "CPD a,X", "1A.A3.d0.", 8, 0, "e8" }, + { "CPD a,Y", "CD.A3.d0.", 8, 0, "e8" }, + { "CPD a", "1A.93g0", 8, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"NEGA", "", "", "COMA", "LSRA", "", "RORA", "ASRA", +"ASLA", "ROLA", "DECA", "", "INCA", "TSTA", "", "CLRA", +NULL }; + +static const char *const cval[] = { +"NEGB", "", "", "COMB", "LSRB", "", "RORB", "ASRB", +"ASLB", "ROLB", "DECB", "", "INCB", "TSTB", "", "CLRB", +NULL }; + +static const char *const dval[] = { +"NEG", "", "", "COM", "LSR", "", "ROR", "ASR", +"ASL", "ROL", "DEC", "", "INC", "TST", "JMP", "CLR", +NULL }; + +static const char *const eval[] = { +"SUBA", "CMPA", "SBCA", "", "ANDA", "BITA", "LDAA", "", +"EORA", "ADCA", "ORAA", "ADDA", +NULL }; + +static const char *const fval[] = { +"SUBB", "CMPB", "SBCB", "", "ANDB", "BITB", "LDAB", "", +"EORB", "ADCB", "ORAB", "ADDB", +NULL }; + +static const char *const gval[] = { +"INX", "DEX", "CLV", "SEV", "CLC", "SEC", "CLI", "SEI", +NULL }; + +static const char *const hval[] = { +"BRA", "", "BHI", "BLS", "BCC", "BCS", "BNE", "BEQ", +"BVC", "BVS", "BPL", "BMI", "BGE", "BLT", "BGT", "BLE", +NULL }; + +static const char *const ival[] = { +"TSX", "INS", "PULA", "PULB", "DES", "TXS", "PSHA", +"PSHB", "", "RTS", "", "RTI", "", "", "WAI", "SWI", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival +}; + +static int match_mc6800(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'i') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_mc6800(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': genb(vs[i] >> 8, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'g': if (vs[i] <= 255) { + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + } else { + genb(b | 0x20, s_pline_ep); + genb(vs[i] >> 8, s_pline_ep); + genb(vs[i], s_pline_ep); + } + break; + case 'h': b = (vs[i] - savepc - 2); + break; + case 'i': b = (vs[i] - savepc - 4); + break; + case 'j': b = (vs[i] - savepc - 5); + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_mc6800(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_mc6800(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'i') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_mc6800 = { + .id = "mc6800", + .descr = "Motorola 6800", + .matcht = s_matchtab_mc6800, + .matchf = match_mc6800, + .genf = gen_mc6800, + .pat_char_rewind = pat_char_rewind_mc6800, + .pat_next_str = pat_next_str_mc6800, + .mask = 3 +}; + +const struct target s_target_mc6801 = { + .id = "mc6801", + .descr = "Motorola 6801", + .matcht = s_matchtab_mc6800, + .matchf = match_mc6800, + .genf = gen_mc6800, + .pat_char_rewind = pat_char_rewind_mc6800, + .pat_next_str = pat_next_str_mc6800, + .mask = 5 +}; + +const struct target s_target_m68hc11 = { + .id = "m68hc11", + .descr = "Motorola 68HC11", + .matcht = s_matchtab_mc6800, + .matchf = match_mc6800, + .genf = gen_mc6800, + .pat_char_rewind = pat_char_rewind_mc6800, + .pat_next_str = pat_next_str_mc6800, + .mask = 13 +}; diff --git a/Tools/unix/uz80as/mos6502.c b/Tools/unix/uz80as/mos6502.c new file mode 100644 index 00000000..f1041b24 --- /dev/null +++ b/Tools/unix/uz80as/mos6502.c @@ -0,0 +1,385 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * MOS Technology 6502. + * Rockwell R6501. + * California Micro Devices G65SC02. + * Rockwell R65C02. + * Rockwell R65C29. + * Western Design Center W65C02S. + * =========================================================================== + */ + +/* mos6502, the original + * + * g65sc02 California Micro Devices, adds to mos6502: + * - zp ADC,AND,CMP,EOR,LDA,ORA,SBC,STA + * - DEC A, INC A + * - JMP (abs,X) + * - BRA + * - PHX,PHY,PLX,PLY + * - STZ + * - TRB + * - TSB + * - More addressing modes for BIT, etc + * + * r6501 Rockwell, adds to mos6502: + * - BBR, BBS + * - RMB, SMB + * + * r65c02 Rockwell, adds the instructions of the g65sc02 and r6501 + * + * r65c29 Rockwell, adds to r65c02: + * - MUL + * + * w65c02s Western Design Center, adds to r65c02: + * - STP,WAI + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ORA,AND,EOR,ADC,STA,LDA,CMP,SBC + * c: ORA,AND,EOR,ADC,LDA,CMP,SBC + * d: PHP,CLC,PLP,SEC,PHA,CLI,PLA,SEI, + * DEY,TYA,TAY,CLV,INY,CLD,INX,SED + * e: ASL,ROL,LSR,ROR + * f: DEC, INC + * g: BPL,BMI,BVC,BVS,BCC,BCS,BNE,BEQ + * h: TXA,TXS,TAX,TSX,DEX,NOP + * i: CPY,CPX + * j: TSB,TRB + * k: BBR0,BBR1,BBR2,BBR3,BBR4,BBR5,BBR6,BBR6, + * BBS0,BBS1,BBS2,BBS3,BBS4,BBS5,BBS6,BBS7 + * l: RMB0,RMB1,RMB2,RMB3,RMB4,RMB5,EMB6,RMB7, + * SMB0,SMB1,SMB2,SMB3,SMB4,SMB5,SMB6,SMB7 + * m: PHY,PLY + * n: PHX,PLX + * o: INC, DEC + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 5) | lastbyte + * g: if op <= $FF output last byte and then op as 8 bit value; + * else output (lastbyte | 0x08) and output op as word + * (no '.' should follow) + * h: (op << 4) | lastbyte + * i: relative jump to op (-2) + * j: if op <= $FF output $64 and op as 8 bit + * else output $9C and op as word + * (no '.' should follow) + * k: if op <= $FF ouput $74 and op as 8 bit + * else output $9E and op as word + * (no '.' should follow) + * l: relative jump to op (-3) + */ + +static const struct matchtab s_matchtab_mos6502[] = { + { "BRK", "00.", 1, 0 }, + { "JSR a", "20.e0", 1, 0 }, + { "RTI", "40.", 1, 0 }, + { "RTS", "60.", 1, 0 }, + { "h", "8Ah0.", 1, 0 }, + { "d", "08h0.", 1, 0 }, + { "c #a", "09f0.d1.", 1, 0, "e8" }, + { "b (a,X)", "01f0.d1.", 1, 0, "e8" }, + { "b (a),Y", "11f0.d1.", 1, 0, "e8" }, + { "b (a)", "12f0.d1.", 2, 0, "e8" }, + { "b a", "05f0g1", 1, 0 }, + { "b a,X", "15f0g1", 1, 0 }, + { "b a,Y", "19f0.e1", 1, 0 }, + { "e A", "0Af0.", 1, 0 }, + { "e a", "06f0g1", 1, 0 }, + { "e a,X", "16f0g1", 1, 0 }, + { "STX a", "86g0", 1, 0 }, + { "STX a,Y", "96.d0.", 1, 0, "e8" }, + { "LDX #a", "A2.d0.", 1, 0, "e8" }, + { "LDX a", "A6g0", 1, 0 }, + { "LDX a,Y", "B6g0", 1, 0 }, + { "o A", "1Af0.", 2, 0 }, + { "f a", "C6f0g1", 1, 0 }, + { "f a,X", "D6f0g1", 1, 0 }, + { "g a", "10f0.i1.", 1, 0, "r8" }, + { "BIT #a", "89.d0.", 2, 0, "e8" }, + { "BIT a", "24g0", 1, 0 }, + { "BIT a,X", "34g0", 2, 0 }, + { "JMP (a)", "6C.e0", 1, 0 }, + { "JMP (a,X)", "7C.e0", 2, 0 }, + { "JMP a", "4C.e0", 1, 0 }, + { "STY a", "84g0", 1, 0 }, + { "STY a,X", "94.d0.", 1, 0, "e8" }, + { "LDY #a", "A0.d0.", 1, 0, "e8" }, + { "LDY a", "A4g0", 1, 0 }, + { "LDY a,X", "B4g0", 1, 0 }, + { "i #a", "C0f0.d1.", 1, 0, "e8" }, + { "i a", "C4f0g1", 1, 0 }, + { "j a", "04h0g1", 2, 0 }, + { "k a,a", "0Fh0.d1.l2.", 4, 0, "e8r8" }, + { "l a", "07h0.d1.", 4, 0, "e8" }, + { "m", "5Af0.", 2, 0 }, + { "n", "DAf0.", 2, 0 }, + { "BRA a", "80.i0.", 2, 0, "r8" }, + { "STZ a,X", "k1", 2, 0 }, + { "STZ a", "j1", 2, 0 }, + { "MUL", "02.", 8, 0 }, + { "WAI", "CB.", 16, 0 }, + { "STP", "DB.", 16, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { + "ORA", "AND", "EOR", "ADC", + "STA", "LDA", "CMP", "SBC", + NULL +}; + +static const char *const cval[] = { + "ORA", "AND", "EOR", "ADC", + "", "LDA", "CMP", "SBC", NULL +}; + +static const char *const dval[] = { + "PHP", "CLC", "PLP", "SEC", + "PHA", "CLI", "PLA", "SEI", + "DEY", "TYA", "TAY", "CLV", + "INY", "CLD", "INX", "SED", + NULL +}; + + +static const char *const eval[] = { + "ASL", "ROL", "LSR", "ROR", + NULL +}; + +static const char *const fval[] = { + "DEC", "INC", + NULL +}; + +static const char *const gval[] = { + "BPL", "BMI", "BVC", "BVS", + "BCC", "BCS", "BNE", "BEQ", + NULL +}; + +static const char *const hval[] = { + "TXA", "TXS", "TAX", "TSX", + "DEX", "", "NOP", + NULL +}; + +static const char *const ival[] = { + "CPY", "CPX", + NULL +}; + +static const char *const jval[] = { + "TSB", "TRB", + NULL +}; + +static const char *const kval[] = { + "BBR0", "BBR1", "BBR2", "BBR3", + "BBR4", "BBR5", "BBR6", "BBR7", + "BBS0", "BBS1", "BBS2", "BBS3", + "BBS4", "BBS5", "BBS6", "BBS7", + NULL +}; + +static const char *const lval[] = { + "RMB0", "RMB1", "RMB2", "RMB3", + "RMB4", "RMB5", "RMB6", "RMB7", + "SMB0", "SMB1", "SMB2", "SMB3", + "SMB4", "SMB5", "SMB6", "SMB7", + NULL +}; + +static const char *const mval[] = { + "PHY", "PLY", + NULL +}; + +static const char *const nval[] = { + "PHX", "PLX", + NULL +}; + +static const char *const oval[] = { + "INC", "DEC", + NULL +}; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival, jval, kval, + lval, mval, nval, oval +}; + +static int match_mos6502(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'o') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_mos6502(int *eb, char p, const int *vs, int i, int savepc) +{ + int b, w; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 5); break; + case 'g': w = vs[i] & 0xffff; + if (w <= 0xff) { + genb(b, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + } else { + b |= 0x08; + genb(b, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'h': b |= (vs[i] << 4); break; + case 'i': b = (vs[i] - savepc - 2); break; + case 'j': w = vs[i] & 0xffff; + if (w <= 0xff) { + genb(0x64, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + } else { + genb(0x9C, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'k': w = vs[i] & 0xffff; + if (w <= 0xff) { + genb(0x74, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + } else { + genb(0x9E, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'l': b = (vs[i] - savepc - 3); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_mos6502(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_mos6502(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'o') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_mos6502 = { + .id = "mos6502", + .descr = "MOS Technology 6502", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 1 +}; + +const struct target s_target_r6501 = { + .id = "r6501", + .descr = "Rockwell R6501", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 5 +}; + +const struct target s_target_g65sc02 = { + .id = "g65sc02", + .descr = "California Micro Devices G65SC02", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 3 +}; + +const struct target s_target_r65c02 = { + .id = "r65c02", + .descr = "Rockwell R65C02", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 7 +}; + +const struct target s_target_r65c29 = { + .id = "r65c29", + .descr = "Rockwell R65C29, R65C00/21", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 15 +}; + +const struct target s_target_w65c02s = { + .id = "w65c02s", + .descr = "Western Design Center W65C02S", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 027 +}; diff --git a/Tools/unix/uz80as/ngetopt.c b/Tools/unix/uz80as/ngetopt.c new file mode 100644 index 00000000..c0404012 --- /dev/null +++ b/Tools/unix/uz80as/ngetopt.c @@ -0,0 +1,237 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Handling of command line options, similar to getopt. + * =========================================================================== + */ + +/* + * Changes: + * + * - Jul 22 2018: long options without short option character recognized. + * + */ + +#include "ngetopt.h" + +#ifndef STRING_H +#include +#endif + +static int find_short_opt(int val, struct ngetopt_opt *ops) +{ + int i; + + i = 0; + while (ops[i].name != NULL) { + if (ops[i].val > 0 && ops[i].val == val) + return i; + i++; + } + + return -1; +} + +static int find_long_opt(char *str, struct ngetopt_opt *ops) +{ + int i; + const char *p, *q; + + i = 0; + while (ops[i].name != NULL) { + p = ops[i].name; + q = str; + while (*p != '\0' && *p == *q) { + p++; + q++; + } + if (*p == '\0' && (*q == '\0' || *q == '=')) { + return i; + } + i++; + } + + return -1; +} + +void ngetopt_init(struct ngetopt *p, int argc, char *const *argv, + struct ngetopt_opt *ops) +{ + p->argc = argc; + p->argv = argv; + p->ops = ops; + p->optind = 1; + p->subind = 0; + strcpy(p->str, "-X"); +} + +static int get_short_opt(struct ngetopt *p) +{ + int i; + char *opt; + + opt = p->argv[p->optind]; + i = find_short_opt(opt[p->subind], p->ops); + if (i < 0) { + /* unrecognized option */ + p->str[1] = (char) opt[p->subind]; + p->optarg = p->str; + p->subind++; + return '?'; + } + + if (!p->ops[i].has_arg) { + /* it's ok */ + p->subind++; + return p->ops[i].val; + } + + /* needs an argument */ + if (opt[p->subind + 1] != '\0') { + /* the argument is the suffix */ + p->optarg = &opt[p->subind + 1]; + p->subind = 0; + p->optind++; + return p->ops[i].val; + } + + /* the argument is the next token */ + p->optind++; + p->subind = 0; + if (p->optind < p->argc) { + p->optarg = p->argv[p->optind]; + p->optind++; + return p->ops[i].val; + } + + /* ups, argument missing */ + p->str[1] = (char) p->ops[i].val; + p->optarg = p->str; + return ':'; +} + +static int get_opt(struct ngetopt *p) +{ + int i; + char *opt, *optnext; + + /* all arguments consumed */ + if (p->optind >= p->argc) + return -1; + + opt = p->argv[p->optind]; + if (opt[0] != '-') { + /* non option */ + return -1; + } + + /* - */ + if (opt[1] == '\0') { + /* stdin */ + return -1; + } + + if (opt[1] != '-') { + /* -xxxxx */ + p->subind = 1; + return get_short_opt(p); + } + + /* -- */ + if (opt[2] == '\0') { + /* found "--" */ + p->optind++; + return -1; + } + + /* long option */ + i = find_long_opt(&opt[2], p->ops); + if (i < 0) { + /* not found */ + p->optind++; + p->optarg = opt; + while (*opt != '\0' && *opt != '=') { + opt++; + } + *opt = '\0'; + return '?'; + } + + /* found, go to end of option */ + optnext = opt + 2 + strlen(p->ops[i].name); + + if (*optnext == '\0' && !p->ops[i].has_arg) { + /* doesn't need arguments */ + p->optind++; + p->optstr = opt + 2; + return p->ops[i].val; + } + + if (*optnext == '=' && !p->ops[i].has_arg) { + /* does not need arguments but argument supplied */ + *optnext = '\0'; + p->optarg = opt; + return ';'; + } + + /* the argument is the next token */ + if (*optnext == '\0') { + p->optind++; + if (p->optind < p->argc) { + p->optstr = opt + 2; + p->optarg = p->argv[p->optind]; + p->optind++; + return p->ops[i].val; + } + + /* ups, argument missing */ + p->optarg = opt; + p->optind++; + return ':'; + } + + /* *optnext == '=' */ + *optnext = '\0'; + p->optstr = opt + 2; + p->optarg = optnext + 1; + p->optind++; + return p->ops[i].val; +} + +/* + * If ok: + * + * - For a long option with a zero value single character option, 0 is + * returned, optstr is the string of the long option (without '-' or '--') + * and optarg is the option argument or NULL. + * + * - For anything else the single option character is returned and optarg + * is the option argument or NULL. + * + * If the option is not recognized, '?' is returned, and optarg is the + * literal string of the option not recognized (already with '-' or '--' + * prefixed). + * + * If the option is recognized but the argument is missing, ':' is + * returned and optarg is the option as supplied (with '-' or '--' prefixed). + * + * If the option is recognized and it is a long option followed by '=', but the + * option does not take arguments, ';' is returned and optarg is the option + * (with '-' or '--' prefixed). + * + * -1 is returned if no more options. + */ +int ngetopt_next(struct ngetopt *p) +{ + if (p->subind == 0) + return get_opt(p); + + /* p->subind > 0 */ + if (p->argv[p->optind][p->subind] != '\0') + return get_short_opt(p); + + /* no more options in this list of short options */ + p->subind = 0; + p->optind++; + return get_opt(p); +} diff --git a/Tools/unix/uz80as/ngetopt.h b/Tools/unix/uz80as/ngetopt.h new file mode 100644 index 00000000..31ff8a60 --- /dev/null +++ b/Tools/unix/uz80as/ngetopt.h @@ -0,0 +1,40 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Handling of command line options, similar to getopt. + * =========================================================================== + */ + +#ifndef NGETOPT_H +#define NGETOPT_H + +/* + * Changelog: + * + * - Jul 21 2018: long options without short option character recognized. + * + */ + +struct ngetopt_opt { + const char *name; + int has_arg; + int val; +}; + +struct ngetopt { + char *optstr; + char *optarg; + /* private */ + int optind; + int argc; + char *const *argv; + struct ngetopt_opt *ops; + int subind; + char str[3]; +}; + +void ngetopt_init(struct ngetopt *p, int argc, char *const *argv, + struct ngetopt_opt *ops); +int ngetopt_next(struct ngetopt *p); + +#endif diff --git a/Tools/unix/uz80as/options.c b/Tools/unix/uz80as/options.c new file mode 100644 index 00000000..c3c24cd2 --- /dev/null +++ b/Tools/unix/uz80as/options.c @@ -0,0 +1,33 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Global options, normally coming from the command line. + * =========================================================================== + */ + +#include "config.h" +#include "options.h" +#include "err.h" + +const char *s_asmfname; /* Name of source file. */ +const char *s_objfname; /* Name of generated binary file. */ +const char *s_lstfname; /* Name of listing file. */ +const char *s_target_id = "z80"; /* ID of target */ +int s_listing = 1; /* If we generate the listing file or not. */ +int s_extended_op = 0; /* Allow extended instruction syntax. */ +int s_undocumented_op = 0; /* Allow undocumented instructions. */ +int s_mem_fillval = 0; /* Default value to fill the 64K memory. */ + +/* Command line macro definitions. */ +struct predef *s_predefs; + +/* Predefine a macro in the command line that must persist between passes. */ +void predefine(const char *text) +{ + struct predef *pdef; + + pdef = emalloc(sizeof(*pdef)); + pdef->name = text; + pdef->next = s_predefs; + s_predefs = pdef; +} diff --git a/Tools/unix/uz80as/options.h b/Tools/unix/uz80as/options.h new file mode 100644 index 00000000..c23ed913 --- /dev/null +++ b/Tools/unix/uz80as/options.h @@ -0,0 +1,29 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Global options, normally coming from the command line. + * =========================================================================== + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +/* Predefined macro at the command line. */ +struct predef { + struct predef *next; + const char *name; +}; + +extern const char *s_asmfname; +extern const char *s_objfname; +extern const char *s_lstfname; +extern const char *s_target_id; +extern int s_listing; +extern int s_extended_op; +extern int s_undocumented_op; +extern int s_mem_fillval; +extern struct predef *s_predefs; + +void predefine(const char *name); + +#endif diff --git a/Tools/unix/uz80as/pp.c b/Tools/unix/uz80as/pp.c new file mode 100644 index 00000000..b2d6a067 --- /dev/null +++ b/Tools/unix/uz80as/pp.c @@ -0,0 +1,741 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Preprocessor. + * =========================================================================== + */ + +#include "config.h" +#include "pp.h" +#include "utils.h" +#include "err.h" +#include "incl.h" +#include "expr.h" +#include "exprint.h" + +#ifndef CTYPE_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +/* Max number of macros. */ +#define NMACROS 1000 + +/* Closest prime to NMACROS / 4. */ +#define MACTABSZ 241 + +/* Max number of macro arguments. */ +#define NPARAMS 20 + +#define DEFINESTR "DEFINE" +#define DEFCONTSTR "DEFCONT" +#define INCLUDESTR "INCLUDE" +#define IFSTR "IF" +#define IFDEFSTR "IFDEF" +#define IFNDEFSTR "IFNDEF" +#define ENDIFSTR "ENDIF" +#define ELSESTR "ELSE" + +/* + * Macro. + * + * For example, the macro: + * + * #define SUM(a,b) (a+b) + * + * is: + * + * name = SUM + * pars = a\0b\0 + * ppars[0] points to &pars[0], that is to "a" + * ppars[1] points to &pars[2], that is to "b" + * npars = 2 + * text is "(a+b)" + */ +struct macro { + struct macro *next; /* Next in hash chain. */ + char *name; /* Identifier. */ + char *pars; /* String with params separated by '\0'. */ + char *text; /* Text to expand. */ + char *ppars[NPARAMS]; /* Pointers to the beginning of each param. */ + int npars; /* Valid number of params in ppars. */ +}; + +/* Hash table of preprocessor symbols. */ +static struct macro *s_mactab[MACTABSZ]; + +/* Preprocessing line buffers. */ +static char s_ppbuf[2][LINESZ]; + +/* If we are discarding lines; if not 0, level of if. */ +int s_skipon; + +/* Number of nested #if or #ifdef or #ifndef. */ +static int s_nifs; + +/* Last defined macro. */ +static struct macro *s_lastmac; + +/* Number of macros in table. */ +static int s_nmacs; + +/* The preprocessed line, points to one of s_ppbuf. */ +char *s_pline; + +/* Current program counter. */ +int s_pc; + +/* Current pass. */ +int s_pass; + + +/* Only valid while in the call to pp_line(). */ +static const char *s_line; /* original line */ +static const char *s_line_ep; /* pointer inside s_line for error reporting */ + +/* + * Copy [p, q[ to [dp, dq[. + */ +static char *copypp(char *dp, char *dq, const char *p, const char *q) +{ + while (dp < dq && p < q) + *dp++ = *p++; + return dp; +} + +/* + * Find the 'argnum' argument in 'args' and return a pointer to it. + * + * 'args' is a list of arguments "([id [,id]*). + * 'argnum' is the argument number to find. + * + * Return not found. + */ +static const char *findarg(const char *args, int argnum) +{ + if (*args == '(') { + do { + args++; + if (argnum == 0) + return args; + argnum--; + while (*args != '\0' && *args != ',' + && *args != ')') + { + args++; + } + } while (*args == ','); + } + return NULL; +} + +/* + * Find the 'argnum' argument in 'args' and copy it to [dp, dq[. + * + * 'args' points to a list of arguments "([id [,id]*). + * 'argnum' is the argument number to copy. + * + * Return the new 'dp' after copying. + */ +static char *copyarg(char *dp, char *dq, const char *args, int argnum) +{ + const char *p; + + p = findarg(args, argnum); + if (p == NULL) + return dp; + + while (dp < dq && *p != '\0' && *p != ',' && *p != ')') + *dp++ = *p++; + return dp; +} + +/* + * Sees if [idp, idq[ is a parameter of the macro 'pps'. + * If it is, return the number of parameter. + * Else return -1. + */ +static int findparam(const char *idp, const char *idq, struct macro *pps) +{ + int i; + const char *p, *r; + + for (i = 0; i < pps->npars; i++) { + p = pps->ppars[i]; + r = idp; + while (*p != '\0' && r < idq && *p == *r) { + p++; + r++; + } + if (*p == '\0' && r == idq) + return i; + } + return -1; +} + +/* + * Lookup the string in [p, q[ in 's_mactab'. + * Return the symbol or NULL if it is not in the table. + */ +static struct macro *pplookup(const char *p, const char *q) +{ + int h; + struct macro *nod; + + h = hash(p, q, MACTABSZ); + for (nod = s_mactab[h]; nod != NULL; nod = nod->next) + if (scmp(p, q, nod->name) == 0) + return nod; + + return nod; +} + +/* + * Expand macro in [dp, dq[. + * + * 'pps' is the macro to expand. + * 'args' points to the start of the arguments to substitute, if any. + * + * Return new dp. + */ +static char *expandid(char *dp, char *dq, struct macro *pps, const char *args) +{ + const char *p, *q; + int validid, argnum; + + validid = 1; + p = pps->text; + while (*p != '\0' && dp < dq) { + if (isidc0(*p)) { + for (q = p; isidc(*q); q++) + ; + if (validid) { + argnum = findparam(p, q, pps); + if (argnum >= 0) + dp = copyarg(dp, dq, args, argnum); + else + dp = copypp(dp, dq, p, q); + } else { + dp = copypp(dp, dq, p, q); + } + p = q; + validid = 1; + } else { + validid = !isidc(*p); + *dp++ = *p++; + } + } + return dp; +} + +/* + * If 'p' points the the start of an argument list, that is, '(', + * point to one character past the first ')' after 'p'. + * Else return 'p'. + */ +static const char *skipargs(const char *p) +{ + if (*p == '(') { + while (*p != '\0' && *p != ')') + p++; + if (*p == ')') + p++; + } + return p; +} + +/* + * Expand macros found in 'p' (null terminated) into [dp, dq[. + * dq must be writable to put a final '\0'. + */ +static int expand_line(char *dp, char *dq, const char *p) +{ + char *op; + int expanded, validid; + const char *s; + struct macro *nod; + + validid = 1; + expanded = 0; + while (dp < dq && *p != '\0' && *p != ';') { + if (*p == '\'' && *(p + 1) != '\0' && *(p + 2) == '\'') { + /* characters */ + dp = copypp(dp, dq, p, p + 3); + p += 3; + validid = 1; + } else if (*p == '\"') { + /* strings */ + s = p; + p++; + while (*p != '\0' && *p != '\"') + p++; + if (*p == '\"') + p++; + dp = copypp(dp, dq, s, p); + validid = 1; + } else if (isidc0(*p)) { + s = p; + while (isidc(*p)) + p++; + if (validid) { + nod = pplookup(s, p); + if (nod != NULL) { + op = dp; + dp = expandid(dp, dq, nod, p); + expanded = dp != op; + p = skipargs(p); + } else { + dp = copypp(dp, dq, s, p); + } + } else { + dp = copypp(dp, dq, s, p); + } + validid = 1; + } else { + validid = *p != '.' && !isalnum(*p); + *dp++ = *p++; + } + } + *dp = '\0'; + return expanded; +} + +/* + * Expand macros found in 'p' (null terminated). + * Return a pointer to an internal preprocessed line (null terminated). + */ +static char *expand_line0(const char *p) +{ + int iter, expanded; + char *np, *nq, *op; + + iter = 0; + np = &s_ppbuf[iter & 1][0]; + nq = &s_ppbuf[iter & 1][LINESZ - 1]; + expanded = expand_line(np, nq, p); + /* TODO: recursive macro expansion limit */ + while (expanded && iter < 5) { + op = np; + iter++; + np = &s_ppbuf[iter & 1][0]; + nq = &s_ppbuf[iter & 1][LINESZ - 1]; + expanded = expand_line(np, nq, op); + } + return np; +} + +/* + * Check if 'p' starts with the preprocessor directive 'ucq', that must be in + * upper case. + * 'p' can have any case. + * After the preprocessor directive must be a space or '\0'. + * Return 1 if all the above is true. 0 otherwise. + */ +static int isppid(const char *p, const char *ucq) +{ + while (*p != '\0' && *ucq != '\0' && toupper(*p) == *ucq) { + p++; + ucq++; + } + return (*ucq == '\0') && (*p == '\0' || isspace(*p)); +} + +/* + * Define a macro. + * + * [idp, idq[ is the macro id. + * [ap, aq[ is the macro argument list. If ap == aq there are no arguments. + * [tp, tq[ is the macro text. + */ +static void define(const char *idp, const char *idq, + const char *tp, const char *tq, + const char *ap, const char *aq) +{ + int h; + char *p; + struct macro *nod; + + h = hash(idp, idq, MACTABSZ); + for (nod = s_mactab[h]; nod != NULL; nod = nod->next) { + if (scmp(idp, idq, nod->name) == 0) { + /* Already defined. */ + return; + } + } + + s_nmacs++; + if (s_nmacs >= NMACROS) { + eprint(_("maximum number of macros exceeded (%d)\n"), NMACROS); + exit(EXIT_FAILURE); + } + + nod = emalloc((sizeof *nod) + (idq - idp) + (aq - ap) + 2); + nod->text = emalloc(tq - tp + 1); + nod->name = (char *) ((unsigned char *) nod + (sizeof *nod)); + nod->pars = nod->name + (idq - idp + 1); + + copychars(nod->name, idp, idq); + copychars(nod->text, tp, tq); + copychars(nod->pars, ap, aq); + + // printf("DEF %s(%s) %s\n", nod->name, nod->pars, nod->text); + + /* We don't check whether the arguments are different. */ + + /* + * Make ppars point to each argument and null terminate each one. + * Count the number of arguments. + */ + nod->npars = 0; + p = nod->pars; + while (*p != '\0') { + nod->ppars[nod->npars++] = p; + while (*p != '\0' && *p != ',') + p++; + if (*p == ',') + *p++ = '\0'; + } + + nod->next = s_mactab[h]; + s_mactab[h] = nod; + s_lastmac = nod; +} + +/* Add the text [p, q[ to the last macro text. */ +static void defcont(const char *p, const char *q) +{ + char *nt; + size_t len; + + len = strlen(s_lastmac->text); + nt = erealloc(s_lastmac->text, (q - p) + len + 1); + copychars(nt + len, p, q); + s_lastmac->text = nt; +} + +/* + * If 'p' points to a valid identifier start, go to the end of the identifier. + * Else return 'p'. + */ +static const char *getid(const char *p) +{ + if (isidc0(*p)) { + while (isidc(*p)) + p++; + } + return p; +} + +/* Issues error in a macro definition. */ +static void macdeferr(int cmdline, const char *estr, const char *ep) +{ + if (cmdline) { + eprint(_("error in command line macro definition\n")); + } + eprint(estr); + eprcol(s_line, ep); + if (cmdline) { + exit(EXIT_FAILURE); + } else { + newerr(); + } +} + +/* Parse macro definition. */ +static void pmacdef(const char *p, int cmdline) +{ + const char *q, *ap, *aq, *idp, *idq; + + idp = p; + idq = getid(idp); + if (idq == idp) { + macdeferr(cmdline, _("identifier excepted\n"), p); + return; + } + p = idq; + ap = aq = p; + if (*p == '(') { + p++; + ap = p; + while (isidc0(*p)) { + p = getid(p); + if (*p != ',') + break; + p++; + } + if (*p != ')') { + macdeferr(cmdline, _("')' expected\n"), p); + return; + } + aq = p; + p++; + } + if (*p != '\0' && !isspace(*p)) { + macdeferr(cmdline, _("space expected\n"), p); + return; + } + p = skipws(p); + /* go to the end */ + for (q = p; *q != '\0'; q++) + ; + /* go to the first non white from the end */ + while (q > p && isspace(*(q - 1))) + q--; + define(idp, idq, p, q, ap, aq); +} + +/* Parse #define. */ +static void pdefine(const char *p) +{ + p = skipws(p + sizeof(DEFINESTR) - 1); + pmacdef(p, 0); +} + +/* Parse #defcont. */ +static void pdefcont(const char *p) +{ + const char *q; + + p = skipws(p + sizeof(DEFCONTSTR) - 1); + + /* go to the end */ + for (q = p; *q != '\0'; q++) + ; + + /* go to the first non white from the end */ + while (q > p && isspace(*(q - 1))) + q--; + + if (p == q) { + /* nothing to add */ + return; + } + + if (s_lastmac == NULL) { + eprint(_("#DEFCONT without a previous #DEFINE\n")); + eprcol(s_line, s_line_ep); + newerr(); + return; + } + + defcont(p, q); +} + +/* Parse #include. */ +static void pinclude(const char *p) +{ + const char *q; + + p = skipws(p + sizeof(INCLUDESTR) - 1); + if (*p != '\"') { + eprint(_("#INCLUDE expects a filename between quotes\n")); + eprcol(s_line, p); + newerr(); + return; + } + q = ++p; + while (*q != '\0' && *q != '\"') + q++; + if (*q != '\"') { + wprint(_("no terminating quote\n")); + eprcol(s_line, q); + } + pushfile(p, q); +} + +/* + * Parse #ifdef or #ifndef. + * 'idsz' is the length of the string 'ifdef' or 'ifndef', plus '\0'. + * 'ifdef' must be 1 if we are #ifdef, 0 if #ifndef. + */ +static void pifdef(const char *p, size_t idsz, int ifdef) +{ + const char *q; + struct macro *nod; + + s_nifs++; + if (s_skipon) + return; + + p = skipws(p + idsz - 1); + if (!isidc0(*p)) { + s_skipon = s_nifs; + eprint(_("identifier expected\n")); + eprcol(s_line, p); + newerr(); + return; + } + q = p; + while (isidc(*q)) + q++; + nod = pplookup(p, q); + if (ifdef == (nod != NULL)) + s_skipon = 0; + else + s_skipon = s_nifs; +} + +/* Parse #else. */ +static void pelse(const char *p) +{ + if (s_nifs == 0) { + eprint(_("unbalanced #ELSE\n")); + eprcol(s_line, s_line_ep); + newerr(); + return; + } + + if (s_skipon && s_nifs == s_skipon) + s_skipon = 0; + else if (!s_skipon) + s_skipon = s_nifs; +} + +/* Parse #endif. */ +static void pendif(const char *p) +{ + if (s_nifs == 0) { + eprint(_("unbalanced #ENDIF\n")); + eprcol(s_line, s_line_ep); + newerr(); + return; + } + + if (s_skipon && s_nifs == s_skipon) + s_skipon = 0; + s_nifs--; +} + +/* + * Parse #if. + */ +static void pif(const char *p) +{ + int v; + enum expr_ecode ex_ec; + const char *ep; + + s_nifs++; + if (s_skipon) + return; + + p = skipws(p + sizeof(IFSTR) - 1); + if (!expr(p, &v, s_pc, 0, &ex_ec, &ep)) { + s_skipon = 1; + exprint(ex_ec, s_line, ep); + newerr(); + return; + } + + if (v == 0) + s_skipon = s_nifs; + else + s_skipon = 0; +} + +/* + * Parse a preprocessor line. + * 'p' points to the next character after the '#'. + */ +static int +parse_line(const char *p) +{ + if (isppid(p, IFDEFSTR)) { + pifdef(p, sizeof IFDEFSTR, 1); + } else if (isppid(p, IFNDEFSTR)) { + pifdef(p, sizeof IFNDEFSTR, 0); + } else if (isppid(p, IFSTR)) { + pif(p); + } else if (isppid(p, ELSESTR)) { + pelse(p); + } else if (isppid(p, ENDIFSTR)) { + pendif(p); + } else if (s_skipon) { + ; + } else if (isppid(p, INCLUDESTR)) { + pinclude(p); + } else if (isppid(p, DEFINESTR)) { + pdefine(p); + } else if (isppid(p, DEFCONTSTR)) { + pdefcont(p); + } else { + return 0; +/* + eprint(_("unknown preprocessor directive\n")); + eprcol(s_line, s_line_ep); + newerr(); +*/ + } + return 1; +} + +/* + * Preprocess 'line' in 's_pline'. + * In this module, while we are preprocessing: + * s_line is the original line. + * s_line_ep is a pointer inside line that we keep for error reporting. + */ +void pp_line(const char *line) +{ + const char *p; + + s_line = line; + s_line_ep = line; + + p = skipws(line); + if ((*p == '#') || (*p == '.')) { + s_line_ep = p; + if (parse_line(p + 1)) { + s_ppbuf[0][0] = '\0'; + s_pline = &s_ppbuf[0][0]; + return; + } + } + if (s_skipon) { + s_ppbuf[0][0] = '\0'; + s_pline = &s_ppbuf[0][0]; + return; + } + s_pline = expand_line0(line); +} + +/* Reset the module for other passes. */ +void pp_reset(void) +{ + int i; + struct macro *nod, *cur; + + s_nmacs = 0; + s_nifs = 0; + s_skipon = 0; + s_lastmac = NULL; + for (i = 0; i < MACTABSZ; i++) { + nod = s_mactab[i]; + while (nod != NULL) { + cur = nod; + nod = nod->next; + free(cur->text); + free(cur); + } + } + memset(s_mactab, 0, MACTABSZ * sizeof(s_mactab[0])); +} + +void pp_define(const char *mactext) +{ + s_line = mactext; + s_line_ep = mactext; + pmacdef(mactext, 1); + s_lastmac = NULL; +} diff --git a/Tools/unix/uz80as/pp.h b/Tools/unix/uz80as/pp.h new file mode 100644 index 00000000..74a6d6f2 --- /dev/null +++ b/Tools/unix/uz80as/pp.h @@ -0,0 +1,23 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Preprocessor. + * =========================================================================== + */ + +#ifndef PP_H +#define PP_H + +/* Max line length after macro expansion + '\0'. */ +#define LINESZ 512 + +extern char *s_pline; +extern int s_pc; +extern int s_pass; +extern int s_skipon; + +void pp_line(const char *line); +void pp_reset(void); +void pp_define(const char *name); + +#endif diff --git a/Tools/unix/uz80as/prtable.c b/Tools/unix/uz80as/prtable.c new file mode 100644 index 00000000..1c3be97f --- /dev/null +++ b/Tools/unix/uz80as/prtable.c @@ -0,0 +1,343 @@ +#include "prtable.h" +#include "err.h" +#include "targets.h" +#include "uz80as.h" + +#ifndef STDLIB_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +enum { STRSZ = 32 }; + +struct itext { + struct itext *next; + int undoc; + char str[STRSZ]; +}; + +struct ilist { + struct itext *head; + int nelems; +}; + +struct itable { + struct itext **table; + int nelems; +}; + +static char s_buf[STRSZ]; + +static void nomem(const char *str) +{ + eprogname(); + fprintf(stderr, _("not enough memory (%s)\n"), str); + exit(EXIT_FAILURE); +} + +static int compare(const void *pa, const void *pb) +{ + const struct itext * const *ia; + const struct itext * const *ib; + + ia = (const struct itext * const *) pa; + ib = (const struct itext * const *) pb; + return strcmp((*ia)->str, (*ib)->str); +} + +/* + * Returns a new allocated itable of pointers that point to each element in the + * list ilist, alphabetically sorted. + */ +static struct itable *sort_list(struct ilist *ilist) +{ + int n; + struct itable *itable; + struct itext *p; + + if ((itable = calloc(1, sizeof(*itable))) == NULL) { + return NULL; + } + itable->nelems = 0; + + if (ilist->nelems == 0) { + return itable; + } + + itable->table = malloc(ilist->nelems * sizeof(*itable->table)); + if (itable->table == NULL) { + free(itable); + return NULL; + } + + for (n = 0, p = ilist->head; + p != NULL && n < ilist->nelems; + p = p->next, n++) + { + itable->table[n] = p; + } + itable->nelems = n; + + qsort(itable->table, itable->nelems, sizeof(*itable->table), compare); + return itable; +} + +static void print_itable(struct itable *itable, FILE *f) +{ + int i, col; + struct itext *p; + + if (itable == NULL) { + return; + } + + fputs("@multitable @columnfractions .25 .25 .25 .25\n", f); + col = 0; + for (i = 0; i < itable->nelems; i++) { + p = itable->table[i]; + if (col == 0) { + fputs("@item ", f); + } else { + fputs("@tab ", f); + } + if (p->undoc) { + fputs("* ", f); + } + fprintf(f, "%s\n", p->str); + col++; + if (col >= 4) { + col = 0; + } + } + fputs("@end multitable\n", f); +} + +#if 0 +static void print_ilist(struct ilist *ilist, FILE *f) +{ + int col; + struct itext *p; + + if (ilist == NULL) { + return; + } + + fputs("@multitable @columnfractions .25 .25 .25 .25\n", f); + col = 0; + for (p = ilist->head; p != NULL; p = p->next) { + if (col == 0) { + fputs("@item ", f); + } else { + fputs("@tab ", f); + } + if (p->undoc) { + fputs("* ", f); + } + fprintf(f, "%s\n", p->str); + col++; + if (col >= 4) { + col = 0; + } + } + fputs("@end multitable\n", f); +} +#endif + +static void bufset(int i, char c) +{ + if (i >= STRSZ) { + eprogname(); + fputs(_("prtable: please, increase s_buf size\n"), stderr); + exit(EXIT_FAILURE); + } else { + s_buf[i] = c; + } +} + +static void gen_inst2(struct ilist *ilist, char *instr, int undoc) +{ + struct itext *p; + + if ((p = malloc(sizeof(*p))) == NULL) { + nomem("gen_inst2"); + } + + snprintf(p->str, STRSZ, "%s", instr); + p->undoc = undoc; + p->next = ilist->head; + ilist->head = p; + ilist->nelems++; +} + +static void gen_inst(struct ilist *ilist, const struct target *t, + unsigned char undoc, const char *p, size_t bufi, + const char *pr) +{ + size_t bufk; + const char *s; + + while (*p) { + if (!islower(*p)) { + if (*p == '@') { + bufset(bufi++, '@'); + } + bufset(bufi++, *p); + p++; + } else if (*p == 'a') { + if (pr == NULL) { + bufset(bufi++, 'e'); + } else if (pr[0] && pr[1]) { + if (pr[0] == pr[1]) { + bufset(bufi++, pr[0]); + pr += 2; + } else if (isdigit(pr[1])) { + bufset(bufi++, *pr); + pr++; + while (isdigit(*pr)) { + bufset(bufi++, *pr); + pr++; + } + } else { + bufset(bufi++, pr[0]); + bufset(bufi++, pr[1]); + pr += 2; + } + } else { + bufset(bufi++, 'e'); + } + p++; + } else { + break; + } + } + + if (*p == '\0') { + bufset(bufi, '\0'); + gen_inst2(ilist, s_buf, t->mask & undoc); + } else { + t->pat_char_rewind(*p); + while ((s = t->pat_next_str()) != NULL) { + if (s[0] != '\0') { + bufset(bufi, '\0'); + bufk = bufi; + while (*s != '\0') { + bufset(bufk++, *s); + s++; + } + bufset(bufk, '\0'); + gen_inst(ilist, t, undoc, p + 1, bufk, pr); + } + } + } +} + +/* Generate a list of instructions. */ +static struct ilist *gen_list(const struct target *t, unsigned char mask2, + int delta) +{ + int i, pr; + const struct matchtab *mt; + struct ilist *ilist; + + if ((ilist = calloc(1, sizeof(*ilist))) == NULL) { + return NULL; + } + + i = 0; + mt = t->matcht; + while (mt[i].pat != NULL) { + pr = 0; + if (t->mask == 1 && (mt[i].mask & 1)) { + pr = 1; + } else if (delta) { + if ((mt[i].mask & t->mask) && + !(mt[i].mask & mask2)) + { + pr = 1; + } + } else if (t->mask & mt[i].mask) { + pr = 1; + } + if (pr) { + gen_inst(ilist, t, mt[i].undoc, mt[i].pat, + 0, mt[i].pr); + } + i++; + } + + return ilist; +} + +/* + * Prints the instruction set of a target or if target_id is "target2,target1" + * prints the instructions in target2 not in target1. + */ +void print_table(FILE *f, const char *target_id) +{ + struct ilist *ilist; + struct itable *itable; + const struct target *t, *t2; + char target1[STRSZ]; + const char *target2; + unsigned char mask2; + int delta; + + /* check if we have "target" or "target,target" as arguments */ + if ((target2 = strchr(target_id, ',')) != NULL) { + delta = 1; + snprintf(target1, sizeof(target1), "%s", target_id); + target1[target2 - target_id] = '\0'; + target2++; + } else { + delta = 0; + snprintf(target1, sizeof(target1), "%s", target_id); + target2 = NULL; + } + + t = find_target(target1); + if (t == NULL) { + eprogname(); + fprintf(stderr, _("invalid target '%s'\n"), target1); + exit(EXIT_FAILURE); + } + + if (target2) { + t2 = find_target(target2); + if (t2 == NULL) { + eprogname(); + fprintf(stderr, _("invalid target '%s'\n"), target2); + exit(EXIT_FAILURE); + } + if (t->matcht != t2->matcht) { + eprogname(); + fprintf(stderr, _("unrelated targets %s,%s\n"), + target1, target2); + exit(EXIT_FAILURE); + } + mask2 = t2->mask; + } else { + mask2 = 1; + } + + if ((ilist = gen_list(t, mask2, delta)) == NULL) { + nomem("gen_list"); + } + + if ((itable = sort_list(ilist)) == NULL) { + nomem("sort_list"); + } + + print_itable(itable, f); + + /* We don't free ilist nor itable for now, since this is called + * from main and then the program terminated. + */ +} + diff --git a/Tools/unix/uz80as/prtable.h b/Tools/unix/uz80as/prtable.h new file mode 100644 index 00000000..41eb646a --- /dev/null +++ b/Tools/unix/uz80as/prtable.h @@ -0,0 +1,11 @@ +#ifndef PRTABLE_H +#define PRTABLE_H + +#ifndef STDIO_H +#define STDIO_H +#include +#endif + +void print_table(FILE *f, const char *target_id); + +#endif diff --git a/Tools/unix/uz80as/sym.c b/Tools/unix/uz80as/sym.c new file mode 100644 index 00000000..53fc2625 --- /dev/null +++ b/Tools/unix/uz80as/sym.c @@ -0,0 +1,103 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Symbol table for labels. + * =========================================================================== + */ + +#include "config.h" +#include "sym.h" +#include "utils.h" +#include "err.h" + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +/* + * Maximum number of symbols (labels) allowed. + * Must not be more than 64K. + */ +#define NSYMS 15000 + +/* Closest prime to NSYMS / 4. */ +#define SYMTABSZ 3739 + +/* + * Nodes for the s_symtab hash table. + * The symbol at index 0 is never used. + */ +static struct sym s_symlist[NSYMS]; + +/* + * Hash table of indexes into s_symlist. + * 0 means that the bucket is empty. + */ +static unsigned short s_symtab[SYMTABSZ]; + +/* Next free symbol in s_symlist. Note: 0 not used. */ +static int s_nsyms = 1; + +/* + * Lookups the string in [p, q[ in s_symtab. + * If !insert, returns the symbol or NULL if it is not in the table. + * If insert, inserts the symbol in the table if it is not there, and + * sets its .val to 'pc'. + */ +struct sym *lookup(const char *p, const char *q, int insert, int pc) +{ + int h, k; + struct sym *nod; + + if (q - p > SYMLEN - 1) { + /* Label too long, don't add. */ + eprint(_("label too long")); + epchars(p, q); + enl(); + newerr(); + /* + * This would truncate: + * q = p + (SYMLEN - 1); + */ + return NULL; + } + + h = hash(p, q, SYMTABSZ); + for (k = s_symtab[h]; k != 0; k = s_symlist[k].next) { + if (scmp(p, q, s_symlist[k].name) == 0) { + if (insert) { + if (!s_symlist[k].isequ) { + wprint("duplicate label (%s)\n", + s_symlist[k].name); + } + } + return &s_symlist[k]; + } + } + + if (insert) { + if (s_nsyms == NSYMS) { + eprint(_("maximum number of labels exceeded (%d)\n"), + NSYMS); + exit(EXIT_FAILURE); + } + + nod = &s_symlist[s_nsyms]; + nod->next = s_symtab[h]; + s_symtab[h] = (unsigned short) s_nsyms; + s_nsyms++; + + k = 0; + while (p != q && k < SYMLEN - 1) + nod->name[k++] = *p++; + nod->name[k] = '\0'; + nod->val = pc; + return nod; + } + + return NULL; +} diff --git a/Tools/unix/uz80as/sym.h b/Tools/unix/uz80as/sym.h new file mode 100644 index 00000000..43108c68 --- /dev/null +++ b/Tools/unix/uz80as/sym.h @@ -0,0 +1,23 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Symbol table for labels. + * =========================================================================== + */ + +#ifndef SYM_H +#define SYM_H + +/* Max symbol length + '\0'. */ +#define SYMLEN 32 + +struct sym { + char name[SYMLEN]; /* null terminated string */ + int val; /* value of symbol */ + unsigned short next; /* index into symlist; 0 is no next */ + unsigned char isequ; /* if val comes from EQU */ +}; + +struct sym *lookup(const char *p, const char *q, int insert, int pc); + +#endif diff --git a/Tools/unix/uz80as/targets.c b/Tools/unix/uz80as/targets.c new file mode 100644 index 00000000..742ef674 --- /dev/null +++ b/Tools/unix/uz80as/targets.c @@ -0,0 +1,96 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Target list. + * =========================================================================== + */ + +#include "targets.h" +#include "uz80as.h" + +#ifndef STRING_H +#include +#endif + +extern const struct target s_target_z80; +extern const struct target s_target_hd64180; +extern const struct target s_target_gbcpu; +extern const struct target s_target_dp2200; +extern const struct target s_target_dp2200ii; +extern const struct target s_target_i4004; +extern const struct target s_target_i4040; +extern const struct target s_target_i8008; +extern const struct target s_target_i8021; +extern const struct target s_target_i8022; +extern const struct target s_target_i8041; +extern const struct target s_target_i8048; +extern const struct target s_target_i8051; +extern const struct target s_target_i8080; +extern const struct target s_target_i8085; +extern const struct target s_target_mos6502; +extern const struct target s_target_r6501; +extern const struct target s_target_g65sc02; +extern const struct target s_target_r65c02; +extern const struct target s_target_r65c29; +extern const struct target s_target_w65c02s; +extern const struct target s_target_mc6800; +extern const struct target s_target_mc6801; +extern const struct target s_target_m68hc11; + +static const struct target *s_targets[] = { + &s_target_z80, + &s_target_hd64180, + &s_target_gbcpu, + &s_target_dp2200, + &s_target_dp2200ii, + &s_target_i4004, + &s_target_i4040, + &s_target_i8008, + &s_target_i8021, + &s_target_i8022, + &s_target_i8041, + &s_target_i8048, + &s_target_i8051, + &s_target_i8080, + &s_target_i8085, + &s_target_mos6502, + &s_target_r6501, + &s_target_g65sc02, + &s_target_r65c02, + &s_target_r65c29, + &s_target_w65c02s, + &s_target_mc6800, + &s_target_mc6801, + &s_target_m68hc11, + NULL, +}; + +static int s_index; + +const struct target *find_target(const char *id) +{ + const struct target **p; + + for (p = s_targets; *p != NULL; p++) { + if (strcmp(id, (*p)->id) == 0) { + return *p; + } + } + + return NULL; +} + +const struct target *first_target(void) +{ + s_index = 0; + return next_target(); +} + +const struct target *next_target(void) +{ + if (s_targets[s_index] != NULL) { + return s_targets[s_index++]; + } else { + return NULL; + } +} diff --git a/Tools/unix/uz80as/targets.h b/Tools/unix/uz80as/targets.h new file mode 100644 index 00000000..12b6f64c --- /dev/null +++ b/Tools/unix/uz80as/targets.h @@ -0,0 +1,18 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Target list. + * =========================================================================== + */ + +#ifndef TARGETS_H +#define TARGETS_H + +struct target; + +const struct target *find_target(const char *id); + +const struct target *first_target(void); +const struct target *next_target(void); + +#endif diff --git a/Tools/unix/uz80as/test.asm b/Tools/unix/uz80as/test.asm new file mode 100644 index 00000000..cfc0d169 --- /dev/null +++ b/Tools/unix/uz80as/test.asm @@ -0,0 +1,22 @@ +#DEFINE MENU_L(M1,M2,M3,M4,M5,M6,M7,M8,M9,M10) \ +#DEFCONT \ .DB M1 +#DEFCONT \ .DB M2 +#DEFCONT \ .DB M3 +#DEFCONT \ .DW M4 +#DEFCONT \ .DW M5 +#DEFCONT \ .DW M6 +#DEFCONT \ .DW M7 +#DEFCONT \ .DB M8 +#DEFCONT \ .DB M9 +#DEFCONT \ .DB M10 + +KY_CL .equ 1 +MON_LOC .equ 1 +MON_SIZ .equ 1 +BID_USR .equ 1 +MON_SERIAL .equ 1 +BID_CUR .equ 1 + +MENU_S: MENU_L("~Monitor$ ", "M", KY_CL, MON_SERIAL, 1000h, MON_LOC, MON_SIZ, BID_CUR, BID_USR, "Monitor$ ") + + .end diff --git a/Tools/unix/uz80as/test.lst b/Tools/unix/uz80as/test.lst new file mode 100644 index 00000000..94a84b15 --- /dev/null +++ b/Tools/unix/uz80as/test.lst @@ -0,0 +1,38 @@ +0001 0000 #DEFINE MENU_L(M1,M2,M3,M4,M5,M6,M7,M8,M9,M10) \ +0002 0000 #DEFCONT \ .DB M1 +0003 0000 #DEFCONT \ .DB M2 +0004 0000 #DEFCONT \ .DB M3 +0005 0000 #DEFCONT \ .DW M4 +0006 0000 #DEFCONT \ .DW M5 +0007 0000 #DEFCONT \ .DW M6 +0008 0000 #DEFCONT \ .DW M7 +0009 0000 #DEFCONT \ .DB M8 +0010 0000 #DEFCONT \ .DB M9 +0011 0000 #DEFCONT \ .DB M10 +0012 0000 +0013 0000 KY_CL .equ 1 +0014 0000 MON_LOC .equ 1 +0015 0000 MON_SIZ .equ 1 +0016 0000 BID_USR .equ 1 +0017 0000 MON_SERIAL .equ 1 +0018 0000 BID_CUR .equ 1 +0019 0000 +0020 0000 MENU_S: MENU_L("~Monitor$ ", "M", KY_CL, MON_SERIAL, 1000h, MON_LOC, MON_SIZ, BID_CUR, BID_USR, "Monitor$ ") +0020 0000 +0020 0000 7E 4D 6F 6E +0020 0004 69 74 6F 72 +0020 0008 24 20 +0020 000A 4D +0020 000B 01 +0020 000C 01 00 +0020 000E 00 10 +0020 0010 01 00 +0020 0012 01 00 +0020 0014 01 +0020 0015 01 +0020 0016 4D 6F 6E 69 +0020 001A 74 6F 72 24 +0020 001E 20 20 20 20 +0020 0022 20 +0021 0023 +0022 0023 .end diff --git a/Tools/unix/uz80as/test.obj b/Tools/unix/uz80as/test.obj new file mode 100644 index 00000000..3660484d Binary files /dev/null and b/Tools/unix/uz80as/test.obj differ diff --git a/Tools/unix/uz80as/utils.c b/Tools/unix/uz80as/utils.c new file mode 100644 index 00000000..a361f2fc --- /dev/null +++ b/Tools/unix/uz80as/utils.c @@ -0,0 +1,137 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Generic functions. + * =========================================================================== + */ + +#include "config.h" +#include "utils.h" + +#ifndef CTYPE_H +#include +#endif + +#ifndef LIMITS_H +#include +#endif + +/* + * Copy [p, q[ to dst and null terminate dst. + */ +void copychars(char *dst, const char *p, const char *q) +{ +// int i = 0; +// printf("copychars %x->%x to %x \'", p, q, dst); + while (p != q) { +// printf("%c", *p); + *dst++ = *p++; +// i++; + } + *dst = '\0'; +// printf("\' %d %x %d\n", *dst, dst, i); +} + +/* Skip space. */ +const char *skipws(const char *p) +{ + while (isspace(*p)) + p++; + return p; +} + +/* Return 1 if *p is a valid start character for an identifier. */ +int isidc0(char c) +{ + return (c == '_') || isalpha(c); +} + +/* + * Return 1 if *p is a valid character for an identifier. + * Don't use for the first character. + */ +int isidc(char c) +{ + return (c == '_') || (c == '.') || isalnum(c); +} + +/* Hash the string in [p, q[ to give a bucket in symtab. */ +int hash(const char *p, const char *q, unsigned int tabsz) +{ + unsigned int h; + + h = 0; + while (p != q) { + h = 31 * h + (unsigned char) *p; + p++; + } + + return h % tabsz; +} + +/* + * Compare the string in [p, q[ with the null-terminated string s. + * Return 0 if equal. + */ +int scmp(const char *p, const char *q, const char *s) +{ + while (p < q) { + if (*p == *s) { + p++; + s++; + } else if (*s == '\0') { + return 1; + } else if (*p < *s) { + return -1; + } else { + return 1; + } + } + + if (*s == '\0') + return 0; + else + return -1; +} +/* + * Given a hexadecimal character (in upper case), returns its integer value. + * Returns -1 if c is not a hexadecimal character. + */ +int hexvalu(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return (c - 'A') + 10; + else + return -1; +} + +/* + * Given a hexadecimal character, returns its integer value. + * Returns -1 if c is not a hexadecimal character. + */ +int hexval(char c) +{ + if (c >= 'a' && c <= 'f') + return (c - 'a') + 10; + else + return hexvalu(c); +} + +int int_precission(void) +{ + static int bits = 0; + unsigned int i; + + if (bits > 0) + return bits; + + i = INT_MAX; + bits = 0; + while (i) { + bits++; + i >>= 1; + } + return bits; +} diff --git a/Tools/unix/uz80as/utils.h b/Tools/unix/uz80as/utils.h new file mode 100644 index 00000000..2d22db6a --- /dev/null +++ b/Tools/unix/uz80as/utils.h @@ -0,0 +1,26 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Generic functions. + * =========================================================================== + */ + +#ifndef UTILS_H +#define UTILS_H + +#define NELEMS(a) (sizeof(a)/sizeof(a[0])) + +#define XSTR(n) STR(n) +#define STR(n) #n + +void copychars(char *dst, const char *p, const char *q); +int hash(const char *p, const char *q, unsigned int tabsz); +int isidc0(char c); +int isidc(char c); +int scmp(const char *p, const char *q, const char *s); +const char *skipws(const char *p); +int hexvalu(char c); +int hexval(char c); +int int_precission(void); + +#endif diff --git a/Tools/unix/uz80as/uz80as.c b/Tools/unix/uz80as/uz80as.c new file mode 100644 index 00000000..c284e2c0 --- /dev/null +++ b/Tools/unix/uz80as/uz80as.c @@ -0,0 +1,1120 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembler. + * =========================================================================== + */ + +#include "config.h" +#include "uz80as.h" +#include "options.h" +#include "utils.h" +#include "err.h" +#include "incl.h" +#include "sym.h" +#include "expr.h" +#include "exprint.h" +#include "pp.h" +#include "list.h" +#include "targets.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +static const char *d_null(const char *); +static const char *d_block(const char *); +static const char *d_byte(const char *); +static const char *d_chk(const char *); +static const char *d_codes(const char *); +static const char *d_echo(const char *); +static const char *d_eject(const char *); +static const char *d_export(const char *); +static const char *d_end(const char *); +static const char *d_equ(const char *); +static const char *d_fill(const char *); +static const char *d_list(const char *); +static const char *d_lsfirst(const char *); +static const char *d_module(const char *); +static const char *d_msfirst(const char *); +static const char *d_nocodes(const char *); +static const char *d_nolist(const char *); +static const char *d_org(const char *); +static const char *d_set(const char *); +static const char *d_text(const char *); +static const char *d_title(const char *); +static const char *d_word(const char *); + +/* + * Directives. + * This table must be sorted, to allow for binary search. + */ +static struct direc { + const char *name; + const char *(*fun)(const char *); +} s_directab[] = { + { "BLOCK", d_block }, + { "BYTE", d_byte }, + { "CHK", d_chk }, + { "CODES", d_codes }, + { "DB", d_byte }, + { "DS", d_fill }, + { "DW", d_word }, + { "ECHO", d_echo }, + { "EJECT", d_eject }, + { "END", d_end }, + { "EQU", d_equ }, + { "EXPORT", d_export }, + { "FILL", d_fill }, + { "LIST", d_list }, + { "LSFIRST", d_lsfirst }, + { "MODULE", d_module }, + { "MSFIRST", d_msfirst }, + { "NOCODES", d_nocodes }, + { "NOLIST", d_nolist }, + { "NOPAGE", d_null }, + { "ORG", d_org }, + { "PAGE", d_null }, + { "SET", d_set }, + { "TEXT", d_text }, + { "TITLE", d_title }, + { "WORD", d_word }, +}; + +/* The target. */ +const struct target *s_target; + +/* The z80 addressable memory. The object code. */ +static unsigned char s_mem[64 * 1024]; + +/* Program counter min and max ([s_minpc, s_maxpc[). */ +static int s_minpc, s_maxpc; + +/* Original input line. */ +static char s_line[LINESZ]; + +/* Label defined on this line. */ +static struct sym *s_lastsym; + +/* Output words the most significant byte first */ +static int s_msbword; + +/* If we have seen the .END directive. */ +static int s_end_seen; + +/* We have issued the error of generating things after an .END. */ +static int s_gen_after_end; + +/* The empty line, to pass to listing, for compatibility with TASM. */ +static const char *s_empty_line = ""; + +/* Pointer in s_pline for error reporting. */ +const char *s_pline_ep; + +/* We skip characters until endline or backslash or comment. */ +static const char *sync(const char *p) +{ + while (*p != '\0' && *p != '\\' && *p != ';') + p++; + return p; +} + +static FILE *fout; + +/* + * Generates a byte to the output and updates s_pc, s_minpc and s_maxpc. + * Will issue a fatal error if we write beyong 64k. + */ +void genb(int b, const char *ep) +{ + if (s_pass == 0 && s_end_seen && !s_gen_after_end) { + s_gen_after_end = 1; + eprint(_("generating code after .END\n")); + eprcol(s_pline, ep); + newerr(); + } + if (s_minpc < 0) + s_minpc = s_pc; + if (s_pc >= 65536) { + eprint(_("generating code beyond address 65535\n")); + eprcol(s_pline, ep); + exit(EXIT_FAILURE); + } + s_mem[s_pc] = (unsigned char) b; + if (s_pass == 1) + list_genb(b); + if (s_pc < s_minpc) + s_minpc = s_pc; + s_pc++; + if (s_pc > s_maxpc) + s_maxpc = s_pc; + + if (s_pass == 1) { + fwrite(&b, 1, 1, fout); + } +} + +/* + * Generate 'n' as a 16 bit word, little endian or big endian depending on + * s_msbword. + */ +static void genw(int n, const char *ep) +{ + if (s_msbword) + genb(n >> 8, ep); + genb(n, ep); + if (!s_msbword) + genb(n >> 8, ep); +} + +/* + * We have matched an instruction in the table. + * Generate the machine code for the instruction using the generation + * pattern 'p. 'vs are the arguments generated during the matching process. + */ +static void gen(const char *p, const int *vs) +{ + // int w, b, i, savepc; + int b, i, savepc; + const char *p_orig; + + savepc = s_pc; + p_orig = p; + b = 0; +loop: + i = hexvalu(*p); + if (i >= 0) { + p++; + b = (i << 4) | hexval(*p); + } else if (*p == '.') { + genb(b, s_pline_ep); + b = 0; + } else if (*p == '\0') { + return; + } else { + i = *(p + 1) - '0'; + switch (*p) { + case 'b': b |= (vs[i] << 3); break; + case 'c': b |= vs[i]; break; + case 'd': b = vs[i]; break; + case 'e': genb(vs[i] & 0xff, s_pline_ep); + genb(vs[i] >> 8, s_pline_ep); + break; + default: + if (s_target->genf(&b, *p, vs, i, savepc) == -1) { + eprogname(); + fprintf(stderr, + _("fatal: bad pattern %s ('%c')"), + p_orig, *p); + enl(); + exit(EXIT_FAILURE); + } + } + p++; + } + p++; + goto loop; +} + +/* + * Tries to match *p with any of the strings in list. + * If matched, returns the index in list and r points to the position + * in p past the matched string. + */ +int mreg(const char *p, const char *const list[], const char **r) +{ + const char *s; + const char *q; + int i; + + i = 0; + while ((s = list[i++]) != NULL) { + if (*s == '\0') + continue; + q = p; + while (toupper(*q++) == *s++) { + if (*s == '\0') { + if (!isalnum(*q)) { + *r = q; + return i - 1; + } else { + break; + } + } + } + } + return -1; +} + +static int isoctal(int c) +{ + return c >= '0' && c <= '7'; +} + +/* + * Read an octal of 3 digits, being the maximum value 377 (255 decimal); + * Return -1 if there is an error in the syntax. + */ +static int readoctal(const char *p) +{ + int n; + const char *q; + + if (*p >= '0' && *p <= '3' && isoctal(*(p + 1)) && isoctal(*(p + 2))) { + n = 0; + q = p + 3; + while (p < q) { + n *= 8; + n += (*p - '0'); + p++; + } + return n; + } + + return -1; +} + +enum strmode { + STRMODE_ECHO, + STRMODE_NULL, + STRMODE_BYTE, + STRMODE_WORD +}; + +/* + * Generate the string bytes until double quote or null char. + * Return a pointer to the ending double quote character or '\0'. + * 'p must point to the starting double quote. + * If mode: + * STRMODE_ECHO only echo to stderr the characters. + * STRMODE_NULL only parses the string. + * STRMODE_BYTE generate the characters in the binary file as bytes. + * STRMODE_WORD generate the characters in the binary file as words. + */ +static const char *genstr(const char *p, enum strmode mode) +{ + int c; + + for (p = p + 1; *p != '\0' && *p != '\"'; p++) { + c = *p; + if (c == '\\') { + p++; + switch (*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 'b': c = '\b'; break; + case 't': c = '\t'; break; + case 'f': c = '\f'; break; + case '\\': c = '\\'; break; + case '\"': c = '\"'; break; + default: + c = readoctal(p); + if (c < 0) { + eprint(_("bad character escape " + "sequence\n")); + eprcol(s_pline, p - 1); + newerr(); + p--; + } else { + p += 2; + } + } + } + switch (mode) { + case STRMODE_ECHO: fputc(c, stderr); break; + case STRMODE_NULL: break; + case STRMODE_BYTE: genb(c, p); break; + case STRMODE_WORD: genw(c, p); break; + } + } + + return p; +} + +/* Match an instruction. + * If no match returns NULL; else returns one past end of match. + * p should point to no whitespace. + */ +static const char *match(const char *p) +{ + const struct matchtab *mtab; + const char *s, *pp, *q; + int v, n, vi, linepc; + int vs[4]; + + assert(!isspace(*p)); + + mtab = s_target->matcht; + linepc = s_pc; + pp = p; + n = -1; +next: + n++; + s = mtab[n].pat; + if (s == NULL) { + return NULL; + } else if ((s_target->mask & mtab[n].mask) == 0) { + goto next; + } else if (!s_undocumented_op && (s_target->mask & mtab[n].undoc)) { + goto next; + } + p = pp; + vi = 0; +loop: + if (*s == '\0') { + p = skipws(p); + if (*p != ';' && *p != '\0' && *p != '\\') + goto next; + else + goto found; + } else if (*s == ' ') { + if (!isspace(*p)) + goto next; + p = skipws(p); + } else if ((*s == ',' || *s == '(' || *s == ')') && isspace(*p)) { + p = skipws(p); + if (*s != *p) + goto next; + p = skipws(p + 1); + } else if (*s == 'a') { + p = expr(p, &v, linepc, s_pass == 0, NULL, NULL); + if (p == NULL) + return NULL; + vs[vi++] = v; + } else if (*s >= 'b' && *s <= 'z') { + v = s_target->matchf(*s, p, &q); + goto reg; + } else if (*p == *s && *p == ',') { + p = skipws(p + 1); + } else if (toupper(*p) == *s) { + p++; + } else { + goto next; + } +freg: + s++; + goto loop; +reg: + if (v < 0) { + goto next; + } else { + assert(vi < sizeof(vs)); + vs[vi++] = v; + p = q; + } + goto freg; +found: + // printf("%s\n", s_matchtab[n].pat); + gen(mtab[n].gen, vs); + return p; +} + +static const char * +d_null(const char *p) +{ + while (*p != '\0' && *p != '\\') { + if (!isspace(*p)) { + wprint(_("invalid characters after directive\n")); + eprcol(s_pline, p); + return sync(p); + } else { + p++; + } + } + return p; +} + +static const char *d_end(const char *p) +{ + enum expr_ecode ecode; + const char *q; + const char *ep; + + if (s_pass == 0) { + if (s_end_seen) { + eprint(_("duplicate .END\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } else { + s_end_seen = 1; + } + } + + q = expr(p, NULL, s_pc, s_pass == 0, &ecode, &ep); + if (q == NULL && ecode == EXPR_E_NO_EXPR) { + return p; + } else if (q == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } else { + return q; + } +} + +static const char *d_codes(const char *p) +{ + s_codes = 1; + return p; +} + +static const char *d_module(const char *p) +{ + while (*p != '\0' && *p != '\\') { + if (!isspace(*p)) { + wprint(_("invalid characters after directive\n")); + eprcol(s_pline, p); + return sync(p); + } else { + p++; + } + } + return p; +} + +static const char *d_nocodes(const char *p) +{ + s_codes = 0; + return p; +} + +static const char *d_list(const char *p) +{ + s_list_on = 1; + return p; +} + +static const char *d_nolist(const char *p) +{ + s_list_on = 0; + return p; +} + +static const char *d_eject(const char *p) +{ + list_eject(); + return p; +} + +static const char *d_echo(const char *p) +{ + int n; + int mode; + enum expr_ecode ecode; + const char *ep; + + mode = (s_pass == 0) ? STRMODE_NULL : STRMODE_ECHO; + if (*p == '\"') { + p = genstr(p, mode); + if (*p == '\"') { + p++; + } else if (s_pass == 0) { + wprint(_("no terminating quote\n")); + eprcol(s_pline, p); + } + } else if (*p != '\0') { + p = expr(p, &n, s_pc, s_pass == 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + if (mode == STRMODE_ECHO) { + fprintf(stderr, "%d", n); + } + } + return p; +} + +static const char *d_equ(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep; + + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (s_lastsym == NULL) { + eprint(_(".EQU without label\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } else { + /* TODO: check label misalign? */ + s_lastsym->val = n; + s_lastsym->isequ = 1; + } + return p; +} + +static const char *d_set(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep; + + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (s_lastsym == NULL) { + eprint(_(".EQU without label\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } else { + /* TODO: check label misalign? */ + s_lastsym->val = n; + s_lastsym->isequ = 1; + } + return p; +} + +static const char *d_export(const char *p) +{ + /* TODO */ + return NULL; +} + +static const char *d_fill(const char *p) +{ + int n, v, er; + const char *q; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + er = 0; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (n < 0) { + eprint(_("number of positions to fill is negative (%d)\n"), n); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + v = 255; + p = skipws(p); + if (*p == ',') { + p = skipws(p + 1); + q = expr(p, &v, s_pc, s_pass == 0, &ecode, &ep); + if (q == NULL) { + er = 1; + exprint(ecode, s_pline, ep); + newerr(); + } else { + p = q; + } + } + + while (n--) + genb(v, eps); + + if (er) + return NULL; + else + return p; +} + +static const char *d_lsfirst(const char *p) +{ + s_msbword = 0; + return p; +} + +static const char *d_msfirst(const char *p) +{ + s_msbword = 1; + return p; +} + +static const char *d_org(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (n < 0 || n > 65536) { + eprint(_(".ORG address (%d) is not in range [0, 65536]\n"), n); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + s_pc = n; + + /* Change the listing PC so in orgs we print the changed PC. */ + if (s_pass > 0) + list_setpc(s_pc); + + if (s_lastsym != NULL) { + /* TODO: check label misalign? */ + s_lastsym->val = s_pc; + s_lastsym->isequ = 1; + } + + return p; +} + +static const char *d_lst(const char *p, int w) +{ + enum strmode mode; + int n, linepc; + enum expr_ecode ecode; + const char *ep, *eps; + + if (w) + mode = STRMODE_WORD; + else + mode = STRMODE_BYTE; + + linepc = s_pc; +dnlst: + if (*p == '\"') { + p = genstr(p, mode); + if (*p == '\"') { + p++; + } else { + wprint(_("no terminating quote\n")); + eprcol(s_pline, p); + } + } else { + eps = p; + p = expr(p, &n, linepc, s_pass == 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + if (w) + genw(n, eps); + else + genb(n, eps); + } + p = skipws(p); + if (*p == ',') { + p++; + p = skipws(p); + goto dnlst; + } + return p; +} + +static const char *d_byte(const char *p) +{ + return d_lst(p, 0); +} + +static const char *d_word(const char *p) +{ + return d_lst(p, 1); +} + +static const char *d_text(const char *p) +{ + if (*p == '\"') { + p = genstr(p, STRMODE_BYTE); + if (*p == '\"') { + p++; + } else { + wprint(_("no terminating quote\n")); + eprcol(s_pline, p); + } + return p; + } else { + eprint(_(".TEXT directive needs a quoted string argument\n")); + eprcol(s_pline, p); + newerr(); + return NULL; + } +} + +static const char *d_title(const char *p) +{ + return NULL; +} + +static const char *d_block(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + s_pc += n; + if (s_pc < 0 || s_pc > 65536) { + eprint(_("address (%d) set by .BLOCK is not in range " + "[0, 65536]\n"), s_pc); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + return p; +} + +/* a must be < b. */ +static int checksum(int a, int b) +{ + int n; + + assert(a < b); + + n = 0; + while (a < b) + n += s_mem[a++]; + + return n; +} + +static const char *d_chk(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + p = expr(p, &n, s_pc, s_pass == 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + genb(0, eps); + return NULL; + } + + if (s_pass == 0) { + genb(0, s_pline_ep); + } else if (n < 0 || n >= s_pc) { + eprint(_(".CHK address (%d) is not in range [0, %d[\n"), n, + s_pc); + eprcol(s_pline, eps); + newerr(); + genb(0, eps); + } else { + genb(checksum(n, s_pc), eps); + } + + return p; +} + +/* Parses an internal directive (those that start with '.'). + * Returns NULL on error; + * If no error returns position past the parsed directive and arguments. */ +static const char *parse_direc(const char *cp) +{ + const char *cq, *p; + int a, b, m = 0; + + a = 0; + b = NELEMS(s_directab) - 1; + while (a <= b) { + m = (a + b) / 2; + cq = cp; + p = s_directab[m].name; + while (*p != '\0' && toupper(*cq) == *p) { + p++; + cq++; + } + if (*p == '\0' && (*cq == '\0' || isspace(*cq))) + break; + else if (toupper(*cq) < *p) + b = m - 1; + else + a = m + 1; + } + + if (a <= b) { + cq = skipws(cq); + return s_directab[m].fun(cq); + } else { + eprint(_("unrecognized directive\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + return NULL; + } +} + +static void parselin(const char *cp) +{ + int col0, alloweq; + const char *q; + + s_pline_ep = cp; +start: s_lastsym = NULL; + alloweq = 0; + col0 = 1; +loop: + if (*cp == '\0' || *cp == ';') { + return; + } else if (*cp == '\\') { + if (s_pass == 1) { + list_endln(); + list_startln(s_empty_line, curfile()->linenum, s_pc, + nfiles()); + } + cp++; + goto start; + } else if (*cp == '.') { + s_pline_ep = cp; + cp++; + q = parse_direc(cp); + if (q == NULL) { + cp = sync(cp); + } else { + cp = d_null(q); + } + } else if ((*cp == '$' || *cp == '*') && cp[1] == '=') { + /* Alternative form of .ORG: *= or $= */ + cp += 2; + q = d_org(cp); + if (q == NULL) { + cp = sync(cp); + } else { + cp = d_null(q); + } + } else if (*cp == '=' && alloweq) { + /* equ */ + s_pline_ep = cp; + cp++; + q = d_equ(cp); + if (q == NULL) { + cp = sync(cp); + } else { + cp = d_null(q); + } + } else if (isidc0(*cp)) { + if (col0 && *cp != '.') { + /* take label */ + s_pline_ep = cp; + q = cp; + col0 = 0; + while (isidc(*cp)) + cp++; + s_lastsym = lookup(q, cp, s_pass == 0, s_pc); + if (*cp == ':' || isspace(*cp)) { + alloweq = 1; + cp++; + } else if (*cp == '=') { + alloweq = 1; + } + if (s_pass == 1 && !s_lastsym->isequ && + s_lastsym->val != s_pc) + { + eprint(_("misaligned label %s\n"), + s_lastsym->name); + fprintf(stderr, _(" Previous value was %XH, " + "new value %XH."), s_lastsym->val, + s_pc); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } else { + cp = skipws(cp); + s_pline_ep = cp; + q = match(cp); + if (q == NULL) { + eprint(_("syntax error\n")); + newerr(); + cp = sync(cp); + } else { + cp = d_null(q); + } + } + } else if (isspace(*cp)) { + col0 = 0; + while (isspace(*cp)) + cp++; + } else { + eprint(_("unexpected character (%c)\n"), *cp); + eprcol(s_pline, cp); + newerr(); + cp = sync(cp + 1); + } + goto loop; +} + +/* + * Gets a new line into 's_line from 'fin. + * Terminates the line with '\0'. + * Does not read more than LINESZ - 1 characters. + * Does not add a '\n' character, thus a line of length 0 it's possible. + * Always advances to the next line. + * Returns -1 for EOF or the line length. + */ +static int getlin(FILE *fin) +{ + int i, c; + + c = EOF; + i = 0; + while (i < LINESZ - 1) { + c = getc(fin); + if (c == EOF || c == '\n') + break; + s_line[i++] = (char) c; + } + if (c != EOF && c != '\n') { + wprint(_("line too long, truncated to %d characters\n"), + LINESZ); + } + while (c != EOF && c != '\n') + c = getc(fin); + if (i == 0 && c == EOF) + return -1; + s_line[i] = '\0'; + return i; +} + +/* Preinstall the macros defined in the command line. */ +static void install_predefs(void) +{ + struct predef *pdef; + + for (pdef = s_predefs; pdef != NULL; pdef = pdef->next) + pp_define(pdef->name); +} + +static void open_output() +{ + fout = efopen(s_objfname, "wb"); +} + +static void close_output() +{ + if (fclose(fout) == EOF) { + eprint(_("cannot close file %s\n"), s_objfname); + } +} + +/* Do a pass through the source. */ +static void dopass(const char *fname) +{ + fprintf(stderr, "start pass %d\n", s_pass); + + /* Fill memory with default value. */ + if ((s_pass == 0 && s_mem_fillval != 0) || s_pass > 0) { + memset(s_mem, s_mem_fillval, sizeof(s_mem)); + } + + if (s_pass > 0) { + pp_reset(); + list_open(s_lstfname); + s_codes = 1; + s_list_on = 1; + open_output(); + } + + install_predefs(); + s_minpc = -1; + s_maxpc = -1; + s_pc = 0; + s_lastsym = NULL; + s_msbword = 0; + + pushfile(fname, fname + strlen(fname)); + while (nfiles() > 0) { + curfile()->linenum++; + if (getlin(curfile()->fin) >= 0) { + if (s_pass == 1) { + list_startln(s_line, curfile()->linenum, s_pc, + nfiles()); + } + pp_line(s_line); + if (s_pass == 1) + list_skip(s_skipon); + parselin(s_pline); + if (s_pass == 1) + list_endln(); + } else { + popfile(); + } + } + + if (s_pass > 0) { + list_close(); + close_output(); + } +} + +/* Write the object file. */ +static void output () +{ + open_output(); + if (s_minpc < 0) + s_minpc = 0; + if (s_maxpc < 0) + s_maxpc = 0; + + fwrite(s_mem + s_minpc, 1, s_maxpc - s_minpc, fout); + if (ferror(fout)) { + eprint(_("cannot write to file %s\n"), s_objfname); + clearerr(fout); + } + close_output(); +} + +/* Start the assembly using the config in options.c. */ +void uz80as(void) +{ + s_target = find_target(s_target_id); + if (s_target == NULL) { + eprint(_("target '%s' not supported\n"), s_target_id); + exit(EXIT_FAILURE); + } + + for (s_pass = 0; s_nerrors == 0 && s_pass < 2; s_pass++) { + dopass(s_asmfname); + if (s_pass == 0 && !s_end_seen) { + wprint(_("no .END statement in the source\n")); + } + if (s_nerrors == 0) { + if (verbose) printf("Pass %d completed.\n", s_pass + 1); + } + } + + if (s_nerrors > 0) { + exit(EXIT_FAILURE); + } + + // output(); +} diff --git a/Tools/unix/uz80as/uz80as.h b/Tools/unix/uz80as/uz80as.h new file mode 100644 index 00000000..511da085 --- /dev/null +++ b/Tools/unix/uz80as/uz80as.h @@ -0,0 +1,63 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembler. + * =========================================================================== + */ + +#ifndef UZ80AS_H +#define UZ80AS_H + +int verbose; + +/* matchtab.flags */ +enum { + MATCH_F_UNDOC = 1, + MATCH_F_EXTEN = 2, +}; + +/* pat: + * a: expr + * b - z: used by target + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f - z: used by target + * + * pr: + * 8: e8 + * f: e16 + * r: relative jump + */ + +struct matchtab { + const char *pat; + const char *gen; + unsigned char mask; + unsigned char undoc; + const char *pr; +}; + +struct target { + const char *id; + const char *descr; + const struct matchtab *matcht; + int (*matchf)(char c, const char *p, const char **q); + int (*genf)(int *eb, char p, const int *vs, int i, int savepc); + void (*pat_char_rewind)(int c); + const char * (*pat_next_str)(void); + unsigned char mask; +}; + +extern const char *s_pline_ep; + +void genb(int b, const char *ep); +int mreg(const char *p, const char *const list[], const char **r); + +void uz80as(void); + +#endif diff --git a/Tools/unix/uz80as/z80.c b/Tools/unix/uz80as/z80.c new file mode 100644 index 00000000..4e2db514 --- /dev/null +++ b/Tools/unix/uz80as/z80.c @@ -0,0 +1,362 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Zilog Z80 CPU. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: B,C,D,E,H,L,A + * c: IX,IY (must be followed by + or -) + * d: BC,DE,HL,SP + * e: IX,IY + * f: BC,DE,HL,AF + * g: ADD,ADC,SUB,SBC,AND,XOR,OR,CP + * h: INC,DEC + * i: BC,DE,IX,SP + * j: BC,DE,IY,SP + * k: RLC,RRC,RL,RR,SLA,SRA,SRL + * l: BIT,RES,SET + * m: NZ,Z,NC,C,PO,PE,P,M + * n: NZ,Z,NC,C + * o: * + * p: B,C,D,E,IXH,IXL,A + * q: B,C,D,E,IYH,IYL,A + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 4) | lastbyte + * g: (op << 6) | lastbyte + * h: * + * i: relative jump to op + * j: possible value to RST + * k: possible value to IM + * m: check arithmetic used with A register + * n: check arithmetic used without A register + */ + +static const struct matchtab s_matchtab_z80[] = { + { "LD b,b", "40b0c1.", 3, 0 }, + { "LD p,p", "DD.40b0c1.", 1, 1 }, + { "LD q,q", "FD.40b0c1.", 1, 1 }, + { "LD b,(HL)", "46b0.", 3, 0 }, + { "LD b,(e)", "d1.46b0.00.", 3, 0, "ii" }, + { "LD b,(ca)", "d1.46b0.d2.", 3, 0, "ii" }, + { "LD A,I", "ED.57.", 3, 0 }, + { "LD A,R", "ED.5F.", 3, 0 }, + { "LD A,(BC)", "0A.", 3, 0 }, + { "LD A,(DE)", "1A.", 3, 0 }, + { "LD A,(a)", "3A.e0", 3, 0 }, + { "LD b,a", "06b0.d1.", 3, 0, "e8" }, + { "LD p,a", "DD.06b0.d1.", 1, 1, "e8" }, + { "LD q,a", "FD.06b0.d1.", 1, 1, "e8" }, + { "LD I,A", "ED.47.", 3, 0 }, + { "LD R,A", "ED.4F.", 3, 0 }, + { "LD SP,HL", "F9.", 3, 0 }, + { "LD SP,e", "d0.F9.", 3, 0 }, + { "LD HL,(a)", "2A.e0", 3, 0 }, + { "LD d,(a)", "ED.4Bf0.e1", 3, 0 }, + { "LD d,a", "01f0.e1", 3, 0 }, + { "LD e,(a)", "d0.2A.e1", 3, 0 }, + { "LD e,a", "d0.21.e1", 3, 0 }, + { "LD (HL),b", "70c0.", 3, 0 }, + { "LD (HL),a", "36.d0.", 3, 0, "e8" }, + { "LD (BC),A", "02.", 3, 0 }, + { "LD (DE),A", "12.", 3, 0 }, + { "LD (e),b", "d0.70c1.00.", 3, 0, "ii" }, + { "LD (ca),b", "d0.70c2.d1.", 3, 0, "ii" }, + { "LD (e),a", "d0.36.00.d1.", 3, 0, "iie8" }, + { "LD (ca),a", "d0.36.d1.d2.", 3, 0, "iie8" }, + { "LD (a),A", "32.e0", 3, 0 }, + { "LD (a),HL", "22.e0", 3, 0 }, + { "LD (a),d", "ED.43f1.e0", 3, 0 }, + { "LD (a),e", "d1.22.e0", 3, 0 }, + { "PUSH f", "C5f0.", 3, 0 }, + { "PUSH e", "d0.E5.", 3, 0 }, + { "POP f", "C1f0.", 3, 0 }, + { "POP e", "d0.E1.", 3, 0 }, + { "EX DE,HL", "EB.", 3, 0 }, + { "EX AF,AF'", "08.", 3, 0 }, + { "EX (SP),HL", "E3.", 3, 0 }, + { "EX (SP),e", "d0.E3.", 3, 0 }, + { "EXX", "D9.", 3, 0 }, + { "LDI", "ED.A0.", 3, 0 }, + { "LDIR", "ED.B0.", 3, 0 }, + { "LDD", "ED.A8.", 3, 0 }, + { "LDDR", "ED.B8.", 3, 0 }, + { "CPI", "ED.A1.", 3, 0 }, + { "CPIR", "ED.B1.", 3, 0 }, + { "CPD", "ED.A9.", 3, 0 }, + { "CPDR", "ED.B9.", 3, 0 }, + { "ADD HL,d", "09f0.", 3, 0 }, + { "ADD IX,i", "DD.09f0.", 3, 0 }, + { "ADD IY,j", "FD.09f0.", 3, 0 }, + { "ADC HL,d", "ED.4Af0.", 3, 0 }, + { "SBC HL,d", "ED.42f0.", 3, 0 }, + { "g A,b", "m080b0c1.", 3, 0 }, + { "g A,p", "DD.m080b0c1.", 1, 1 }, + { "g A,q", "FD.m080b0c1.", 1, 1 }, + { "g A,(HL)", "m086b0.", 3, 0 }, + { "g A,(ca)", "m0d1.86b0.d2.", 3, 0, "ii" }, + { "g A,a", "m0C6b0.d1.", 3, 0, "e8" }, + { "g b", "n080b0c1.", 3, 0 }, + { "g p", "DD.n080b0c1.", 1, 1 }, + { "g q", "FD.n080b0c1.", 1, 1 }, + { "g (HL)", "n086b0.", 3, 0 }, + { "g (ca)", "n0d1.86b0.d2.", 3, 0, "ii" }, + { "g a", "n0C6b0.d1.", 3, 0, "e8" }, + { "h b", "04b1c0.", 3, 0 }, + { "h p", "DD.04b1c0.", 1, 1 }, + { "h q", "FD.04b1c0.", 1, 1 }, + { "h (HL)", "34c0.", 3, 0 }, + { "h (ca)", "d1.34c0.d2.", 3, 0, "ii" }, + { "h (e)", "d1.34c0.00.", 3, 0, "ii" }, + { "INC d", "03f0.", 3, 0 }, + { "INC e", "d0.23.", 3, 0 }, + { "DEC d", "0Bf0.", 3, 0 }, + { "DEC e", "d0.2B.", 3, 0 }, + { "DAA", "27.", 3, 0 }, + { "CPL", "2F.", 3, 0 }, + { "NEG", "ED.44.", 3, 0 }, + { "CCF", "3F.", 3, 0 }, + { "SCF", "37.", 3, 0 }, + { "NOP", "00.", 3, 0 }, + { "HALT", "76.", 3, 0 }, + { "DI", "F3.", 3, 0 }, + { "EI", "FB.", 3, 0 }, + { "IM a", "ED.k0.", 3, 0, "tt" }, + { "RLCA", "07.", 3, 0 }, + { "RLA", "17.", 3, 0 }, + { "RRCA", "0F.", 3, 0 }, + { "RRA", "1F.", 3, 0 }, + { "SLL b", "CB.30c0.", 1, 1 }, + { "SLL (HL)", "CB.36.", 1, 1 }, + { "SLL (ca)", "d0.CB.d1.36.", 1, 1, "ii" }, + { "SLL (ca),b", "d0.CB.d1.30c2.", 1, 1, "ii" }, + { "k b", "CB.00b0c1.", 3, 0 }, + { "k (HL)", "CB.06b0.", 3, 0 }, + { "k (ca)", "d1.CB.d2.06b0.", 3, 0, "ii" }, + { "k (ca),b", "d1.CB.d2.00b0c3.", 1, 1, "ii" }, + { "RLD", "ED.6F.", 3, 0 }, + { "RRD", "ED.67.", 3, 0 }, + { "l a,b", "CB.00g0b1c2.", 3, 0, "b3" }, + { "l a,(HL)", "CB.06g0b1.", 3, 0, "b3" }, + { "l a,(ca)", "d2.CB.d3.06g0b1.", 3, 0, "b3ii" }, + { "RES a,(ca),b", "d1.CB.d2.80b0c3.", 1, 1, "b3ii" }, + { "SET a,(ca),b", "d1.CB.d2.C0b0c3.", 1, 1, "b3ii" }, + { "JP (HL)", "E9.", 3, 0 }, + { "JP (e)", "d0.E9.", 3, 0 }, + { "JP m,a", "C2b0.e1", 3, 0 }, + { "JP a", "C3.e0", 3, 0 }, + { "JR n,a", "20b0.i1.", 3, 0, "r8" }, + { "JR a", "18.i0.", 3, 0, "r8" }, + { "DJNZ a", "10.i0.", 3, 0, "r8" }, + { "CALL m,a", "C4b0.e1", 3, 0 }, + { "CALL a", "CD.e0", 3, 0 }, + { "RETI", "ED.4D.", 3, 0 }, + { "RETN", "ED.45.", 3, 0 }, + { "RET m", "C0b0.", 3, 0 }, + { "RET", "C9.", 3, 0 }, + { "RST a", "C7j0.", 3, 0, "ss" }, + { "IN b,(C)", "ED.40b0.", 3, 0 }, + { "IN A,(a)", "DB.d0.", 3, 0, "e8" }, + { "IN F,(a)", "ED.70.", 3, 0 }, + { "IN (C)", "ED.70.", 1, 1 }, + { "INI", "ED.A2.", 3, 0 }, + { "INIR", "ED.B2.", 3, 0 }, + { "IND", "ED.AA.", 3, 0 }, + { "INDR", "ED.BA.", 3, 0 }, + { "OUT (C),0", "ED.71.", 1, 1 }, + { "OUT (C),b", "ED.41b0.", 3, 0 }, + { "OUT (a),A", "D3.d0.", 3, 0, "e8" }, + { "OUTI", "ED.A3.", 3, 0 }, + { "OTIR", "ED.B3.", 3, 0 }, + { "OUTD", "ED.AB.", 3, 0 }, + { "OTDR", "ED.BB.", 3, 0 }, + /* hd64180 added instructions */ + { "IN0 b,(a)", "ED.00b0.d1.", 2, 0, "e8" }, + { "OUT0 (a),b", "ED.01b1.d0.", 2, 0, "e8" }, + { "OTDM", "ED.8B.", 2, 0 }, + { "OTDMR", "ED.9B.", 2, 0 }, + { "OTIM", "ED.83.", 2, 0 }, + { "OTIMR", "ED.93.", 2, 0 }, + { "MLT d", "ED.4Cf0.", 2, 0 }, + { "SLP", "ED.76.", 2, 0 }, + { "TST b", "ED.04b0.", 2, 0 }, + { "TST (HL)", "ED.34.", 2, 0 }, + { "TST a", "ED.64.d0.", 2, 0, "e8" }, + { "TSTIO a", "ED.74.d0.", 2, 0, "e8" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { "B", "C", "D", "E", + "H", "L", "", "A", NULL }; +static const char *const cval[] = { "IX", "IY", NULL }; +static const char *const dval[] = { "BC", "DE", "HL", "SP", NULL }; +static const char *const fval[] = { "BC", "DE", "HL", "AF", NULL }; +static const char *const gval[] = { "ADD", "ADC", "SUB", "SBC", + "AND", "XOR", "OR", "CP", NULL }; +static const char *const hval[] = { "INC", "DEC", NULL }; +static const char *const ival[] = { "BC", "DE", "IX", "SP", NULL }; +static const char *const jval[] = { "BC", "DE", "IY", "SP", NULL }; +static const char *const kval[] = { "RLC", "RRC", "RL", "RR", + "SLA", "SRA", "", "SRL", NULL }; +static const char *const lval[] = { "", "BIT", "RES", "SET", NULL }; +static const char *const mval[] = { "NZ", "Z", "NC", "C", + "PO", "PE", "P", "M", NULL }; +static const char *const nval[] = { "NZ", "Z", "NC", "C", NULL }; +static const char *const pval[] = { "B", "C", "D", "E", + "IXH", "IXL", "", "A", NULL }; +static const char *const qval[] = { "B", "C", "D", "E", + "IYH", "IYL", "", "A", NULL }; +static const char *const nullv[] = { NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, dval, fval, + gval, hval, ival, jval, kval, + lval, mval, nval, nullv, pval, + qval +}; + +static int indval(const char *p, int disp, const char **q) +{ + int v; + const char *r; + + v = mreg(p, cval, &r); + if (v >= 0) { + v = (v == 0) ? 0xDD : 0xFD; + while (*r == ' ') r++; + if (!disp || *r == '+' || *r == '-') { + *q = r; + return v; + } + } + return -1; +} + +static int match_z80(char c, const char *p, const char **q) +{ + int v; + + if (c == 'c' || c == 'e') { + v = indval(p, c == 'c', q); + } else if (c <= 'q') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_z80(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 4); break; + case 'g': b |= (vs[i] << 6); break; + case 'i': b = (vs[i] - savepc - 2); break; + case 'j': if (s_pass > 0 && (vs[i] & ~56) != 0) { + eprint(_("invalid RST argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'k': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 2)) { + eprint(_("invalid IM argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b = 0x46; + if (vs[i] == 1) + b = 0x56; + else if (vs[i] == 2) + b = 0x5E; + break; + case 'm': if (s_pass == 0 && !s_extended_op) { + if (vs[i] != 0 && vs[i] != 1 && vs[i] != 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + case 'n': if (s_pass == 0 && !s_extended_op) { + if (vs[i] == 0 || vs[i] == 1 || vs[i] == 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_z80(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_z80(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'q') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_z80 = { + .id = "z80", + .descr = "Zilog Z80", + .matcht = s_matchtab_z80, + .matchf = match_z80, + .genf = gen_z80, + .pat_char_rewind = pat_char_rewind_z80, + .pat_next_str = pat_next_str_z80, + .mask = 1 +}; + +const struct target s_target_hd64180 = { + .id = "hd64180", + .descr = "Hitachi HD64180", + .matcht = s_matchtab_z80, + .matchf = match_z80, + .genf = gen_z80, + .pat_char_rewind = pat_char_rewind_z80, + .pat_next_str = pat_next_str_z80, + .mask = 2 +}; diff --git a/Tools/unix/zx/Makefile b/Tools/unix/zx/Makefile new file mode 100644 index 00000000..bbffe497 --- /dev/null +++ b/Tools/unix/zx/Makefile @@ -0,0 +1,39 @@ +# +# hacked up brute force makefile for linux and osx +# +UNAME := $(shell uname) +ifeq ($(UNAME), Linux) + SUFFIX=linux +endif +ifeq ($(UNAME), Darwin) + SUFFIX=darwin +endif + +DEST = ../../$(UNAME) +CFLAGS = -g # -DDEBUG + +OBJECTS = zx.o cpmdrv.o cpmglob.o cpmparse.o cpmredir.o \ + drdos.o util.o xlt.o zxbdos.o zxcbdos.o zxdbdos.o z80.o +UNUSED = dirent.o + +all: zx + +$(DEST): + mkdir -p $(DEST) + +clean: + -rm -f $(OBJECTS) config.h + +clobber: clean + -rm -f $(DEST)/zx $(DEST)/bios.bin zx + +install: zx $(DEST) + cp bios.bin zx $(DEST) + +$(OBJECTS): config.h + +zx: $(OBJECTS) + $(CC) -o zx $(OBJECTS) + +config.h: config.h.$(SUFFIX) + cp config.h.$(SUFFIX) config.h diff --git a/Tools/unix/zx/bios.bin b/Tools/unix/zx/bios.bin new file mode 100644 index 00000000..b20e62d9 Binary files /dev/null and b/Tools/unix/zx/bios.bin differ diff --git a/Tools/unix/zx/cbops.h b/Tools/unix/zx/cbops.h new file mode 100644 index 00000000..47b69da0 --- /dev/null +++ b/Tools/unix/zx/cbops.h @@ -0,0 +1,172 @@ +/* Emulations of the CB operations of the Z80 instruction set. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define var_t unsigned char t +#define rlc(x) (x=(x<<1)|(x>>7),rflags(x,x&1)) +#define rrc(x) do{var_t=x&1;x=(x>>1)|(t<<7);rflags(x,t);}while(0) +#define rl(x) do{var_t=x>>7;x=(x<<1)|(f&1);rflags(x,t);}while(0) +#define rr(x) do{var_t=x&1;x=(x>>1)|(f<<7);rflags(x,t);}while(0) +#define sla(x) do{var_t=x>>7;x<<=1;rflags(x,t);}while(0) +#define sra(x) do{var_t=x&1;x=((signed char)x)>>1;rflags(x,t);}while(0) +#define sll(x) do{var_t=x>>7;x=(x<<1)|1;rflags(x,t);}while(0) +#define srl(x) do{var_t=x&1;x>>=1;rflags(x,t);}while(0) + +#define rflags(x,c) (f=(c)|(x&0xa8)|((!x)<<6)|parity(x)) + +#define bit(n,x) (f=(f&1)|((x&(1<>3)&7; + switch(op&0xc7){ + case 0x40: bit(n,b); break; + case 0x41: bit(n,c); break; + case 0x42: bit(n,d); break; + case 0x43: bit(n,e); break; + case 0x44: bit(n,h); break; + case 0x45: bit(n,l); break; + case 0x46: tstates+=4;val=fetch(addr);bit(n,val);store(addr,val);break; + case 0x47: bit(n,a); break; + case 0x80: res(n,b); break; + case 0x81: res(n,c); break; + case 0x82: res(n,d); break; + case 0x83: res(n,e); break; + case 0x84: res(n,h); break; + case 0x85: res(n,l); break; + case 0x86: tstates+=4;val=fetch(addr);res(n,val);store(addr,val);break; + case 0x87: res(n,a); break; + case 0xc0: set(n,b); break; + case 0xc1: set(n,c); break; + case 0xc2: set(n,d); break; + case 0xc3: set(n,e); break; + case 0xc4: set(n,h); break; + case 0xc5: set(n,l); break; + case 0xc6: tstates+=4;val=fetch(addr);set(n,val);store(addr,val);break; + case 0xc7: set(n,a); break; + } + } + if(ixoriy)switch(reg){ + case 0:b=val; break; + case 1:c=val; break; + case 2:d=val; break; + case 3:e=val; break; + case 4:h=val; break; + case 5:l=val; break; + case 7:a=val; break; + } +} + +#undef var_t +#undef rlc +#undef rrc +#undef rl +#undef rr +#undef sla +#undef sra +#undef sll +#undef srl +#undef rflags +#undef bit +#undef set +#undef res diff --git a/Tools/unix/zx/config.h.darwin b/Tools/unix/zx/config.h.darwin new file mode 100644 index 00000000..d3a4bdb0 --- /dev/null +++ b/Tools/unix/zx/config.h.darwin @@ -0,0 +1,15 @@ +//#define HAVE_WINDOWS_H +#define HAVE_DIRENT_H +#define HAVE_UTIME_H +#define HAVE_FCNTL_H +#define BINDIR80 getenv("ZXBINDIR") +#define LIBDIR80 getenv("ZXLIBDIR") +#define INCDIR80 getenv("ZXINCDIR") +#define LINUX +#include +#include +#define _S_IFDIR S_IFDIR +#define strcmpi(a,b) strcasecmp(a,b) +//#define WIN32 +//#define WINVER 0x0501 // target Windows XP +//#define _WIN32_WINNNT 0x0501 // target Windows XP diff --git a/Tools/unix/zx/config.h.linux b/Tools/unix/zx/config.h.linux new file mode 100644 index 00000000..bca1a709 --- /dev/null +++ b/Tools/unix/zx/config.h.linux @@ -0,0 +1,16 @@ +//#define HAVE_WINDOWS_H +#define HAVE_DIRENT_H +#define HAVE_UTIME_H +#define HAVE_FCNTL_H +#define HAVE_SYS_VFS_H +#define BINDIR80 getenv("ZXBINDIR") +#define LIBDIR80 getenv("ZXLIBDIR") +#define INCDIR80 getenv("ZXINCDIR") +#define LINUX +#include +#include +#define _S_IFDIR S_IFDIR +#define strcmpi(a,b) strcasecmp(a,b) +//#define WIN32 +//#define WINVER 0x0501 // target Windows XP +//#define _WIN32_WINNNT 0x0501 // target Windows XP diff --git a/Tools/unix/zx/config.h.windows b/Tools/unix/zx/config.h.windows new file mode 100644 index 00000000..8186c485 --- /dev/null +++ b/Tools/unix/zx/config.h.windows @@ -0,0 +1,9 @@ +#define HAVE_WINDOWS_H +//#define HAVE_DIRENT_H +#define HAVE_FCNTL_H +#define BINDIR80 getenv("ZXBINDIR") +#define LIBDIR80 getenv("ZXLIBDIR") +#define INCDIR80 getenv("ZXINCDIR") +#define WIN32 +#define WINVER 0x0501 // target Windows XP +#define _WIN32_WINNNT 0x0501 // target Windows XP diff --git a/Tools/unix/zx/cpm/bios.bin b/Tools/unix/zx/cpm/bios.bin new file mode 100644 index 00000000..b20e62d9 Binary files /dev/null and b/Tools/unix/zx/cpm/bios.bin differ diff --git a/Tools/unix/zx/cpm/bios.com b/Tools/unix/zx/cpm/bios.com new file mode 100644 index 00000000..b20e62d9 Binary files /dev/null and b/Tools/unix/zx/cpm/bios.com differ diff --git a/Tools/unix/zx/cpm/bios.lst b/Tools/unix/zx/cpm/bios.lst new file mode 100644 index 00000000..d3e97405 --- /dev/null +++ b/Tools/unix/zx/cpm/bios.lst @@ -0,0 +1,106 @@ +Z80ASM SuperFast Relocating Macro Assembler Z80ASM 1.30 Page 1 +BIOS Z80 + + 1 ; BIOS / BDOS for the ZXCC environment. + 2 ; + 3 FE00 org 0FE00h + 4 FE00 5A 58 43 43 DEFB 'ZXCC04' ;Serial number + 5 ; + 6 ; Some CP/M programs expect a jump at the start of BDOS, so here it is. + 7 ; + 8 FE06 C3 FE09 BDOS0: JP BDOS1 + 9 + 10 FE09 3E C0 BDOS1: LD A,0C0h + 11 FE0B ED FE DEFB 0EDh,0FEh + 12 FE0D C9 RET + 13 ; + 14 ; Hack for ZMAC. ZMAC is using contents of 0FE22H to establish a memory pointer. + 15 ; It makes no sense. We stuff 04F4CH here because it is known to work... + 16 ; + 17 FE22 org 0FE22H + 18 FE22 4F4C DEFW 04F4CH + 19 ; + 20 ;This is not a real BIOS, so let its code live below the BIOS jumpblock. + 21 ; + 22 FE24 DD 22 FE33 UBIOS: LD (XIX),IX + 23 FE28 DD E1 POP IX ;IX = address of UBIOS function + 24 FE2A 3E C3 LD A,0C3h ;C3h = BIOS call + 25 FE2C ED FE DEFB 0EDh,0FEh + 26 FE2E DD 2A FE33 LD IX,(XIX) + 27 FE32 C9 RET + 28 ; + 29 FE33 0000 XIX: DEFW 0 + 30 + 31 FE35 21 FF03 CBOOT: LD HL,WBOOT0 + 32 FE38 22 0001 LD (1),HL + 33 FE3B 21 FE06 LD HL,BDOS0 + 34 FE3E 22 0006 LD (6),HL + 35 FE41 3E C3 LD A,0C3h + 36 FE43 32 0000 LD (0),A + 37 FE46 32 0005 LD (5),A + 38 FE49 3E C9 LD A,0C9h + 39 FE4B 32 0038 LD (038h),A + 40 FE4E 3E C1 LD A,0C1h ;C1h = program load + 41 FE50 ED FE DEFB 0EDh,0FEh + 42 FE52 21 0000 LD HL,0 + 43 FE55 E5 PUSH HL ;In case called program tries to RET + 44 FE56 C3 0100 JP 0100h + 45 ; + 46 FE59 3E C3 WBOOT: LD A,0C3h ;Program termination + 47 FE5B DD 21 0006 LD IX,6 ;BIOS call 1 + 48 FE5F ED FE DEFB 0EDh,0FEh + 49 FE61 76 HALT + 50 FE62 C3 FE62 JP $ + 51 ; + 52 FEEC org 0FEECh + 53 FEEC FF tmpdrv: defb 0FFh ;Temp drive = current + 54 + 55 + 56 ; + 57 ;TODO: SCB at FE9Ch + 58 ; + Z80ASM SuperFast Relocating Macro Assembler Z80ASM 1.30 Page 2 +BIOS Z80 + + 59 FF00 org 0FF00h + 60 FF00 C3 FE35 JP CBOOT ;FF00 + 61 FF03 C3 FE59 WBOOT0: JP WBOOT ;03 + 62 FF06 CD FE24 CALL UBIOS ;06 + 63 FF09 CD FE24 CALL UBIOS ;09 + 64 FF0C CD FE24 CALL UBIOS ;0C + 65 FF0F CD FE24 CALL UBIOS ;0F + 66 FF12 CD FE24 CALL UBIOS ;12 + 67 FF15 CD FE24 CALL UBIOS ;15 + 68 FF18 CD FE24 CALL UBIOS ;18 + 69 FF1B CD FE24 CALL UBIOS ;1B + 70 FF1E CD FE24 CALL UBIOS ;1E + 71 FF21 CD FE24 CALL UBIOS ;21 + 72 FF24 CD FE24 CALL UBIOS ;24 + 73 FF27 CD FE24 CALL UBIOS ;27 + 74 FF2A CD FE24 CALL UBIOS ;2A + 75 FF2D CD FE24 CALL UBIOS ;2D + 76 FF30 CD FE24 CALL UBIOS ;30 + 77 FF33 CD FE24 CALL UBIOS ;33 + 78 FF36 CD FE24 CALL UBIOS ;36 + 79 FF39 CD FE24 CALL UBIOS ;39 + 80 FF3C CD FE24 CALL UBIOS ;42 + 81 FF3F CD FE24 CALL UBIOS ;45 + 82 FF42 CD FE24 CALL UBIOS ;48 + 83 FF45 CD FE24 CALL UBIOS ;4B + 84 FF48 CD FE24 CALL UBIOS ;4E + 85 FF4B CD FE24 CALL UBIOS ;51 + 86 FF4E CD FE24 CALL UBIOS ;54 + 87 FF51 CD FE24 CALL UBIOS ;57 + 88 FF54 CD FE24 CALL UBIOS ;5A, USERF + 89 FF57 CD FE24 CALL UBIOS ;5D, RESERV1 + 90 FF5A CD FE24 CALL UBIOS ;60, RESERV2 + 91 ; + 92 FFC0 org 0FFC0h ;Space for DPB + 93 FFC0 0020 dpb: defs 20h + 94 + 95 END + 0 Error(s) Detected. + 480 Absolute Bytes. 9 Symbols Detected. +  UBIOS ;27 + 74 FF2A CD FE24 CALL UBIOS ;2A + 75 FF2D CD FE24 \ No newline at end of file diff --git a/Tools/unix/zx/cpm/bios.z80 b/Tools/unix/zx/cpm/bios.z80 new file mode 100644 index 00000000..fb64e891 --- /dev/null +++ b/Tools/unix/zx/cpm/bios.z80 @@ -0,0 +1,96 @@ +; BIOS / BDOS for the ZXCC environment. +; + org 0FE00h + DEFB 'ZXCC04' ;Serial number +; +; Some CP/M programs expect a jump at the start of BDOS, so here it is. +; +BDOS0: JP BDOS1 + +BDOS1: LD A,0C0h + DEFB 0EDh,0FEh + RET +; +; Hack for ZMAC. ZMAC is using contents of 0FE22H to establish a memory pointer. +; It makes no sense. We stuff 04F4CH here because it is known to work... +; + org 0FE22H + DEFW 04F4CH +; +;This is not a real BIOS, so let its code live below the BIOS jumpblock. +; +UBIOS: LD (XIX),IX + POP IX ;IX = address of UBIOS function + LD A,0C3h ;C3h = BIOS call + DEFB 0EDh,0FEh + LD IX,(XIX) + RET +; +XIX: DEFW 0 + +CBOOT: LD HL,WBOOT0 + LD (1),HL + LD HL,BDOS0 + LD (6),HL + LD A,0C3h + LD (0),A + LD (5),A + LD A,0C9h + LD (038h),A + LD A,0C1h ;C1h = program load + DEFB 0EDh,0FEh + LD HL,0 + PUSH HL ;In case called program tries to RET + JP 0100h +; +WBOOT: LD A,0C3h ;Program termination + LD IX,6 ;BIOS call 1 + DEFB 0EDh,0FEh + HALT + JP $ +; + org 0FEECh +tmpdrv: defb 0FFh ;Temp drive = current + + +; +;TODO: SCB at FE9Ch +; + org 0FF00h + JP CBOOT ;FF00 +WBOOT0: JP WBOOT ;03 + CALL UBIOS ;06 + CALL UBIOS ;09 + CALL UBIOS ;0C + CALL UBIOS ;0F + CALL UBIOS ;12 + CALL UBIOS ;15 + CALL UBIOS ;18 + CALL UBIOS ;1B + CALL UBIOS ;1E + CALL UBIOS ;21 + CALL UBIOS ;24 + CALL UBIOS ;27 + CALL UBIOS ;2A + CALL UBIOS ;2D + CALL UBIOS ;30 + CALL UBIOS ;33 + CALL UBIOS ;36 + CALL UBIOS ;39 + CALL UBIOS ;42 + CALL UBIOS ;45 + CALL UBIOS ;48 + CALL UBIOS ;4B + CALL UBIOS ;4E + CALL UBIOS ;51 + CALL UBIOS ;54 + CALL UBIOS ;57 + CALL UBIOS ;5A, USERF + CALL UBIOS ;5D, RESERV1 + CALL UBIOS ;60, RESERV2 +; + org 0FFC0h ;Space for DPB +dpb: defs 20h + + END + diff --git a/Tools/unix/zx/cpmdrv.c b/Tools/unix/zx/cpmdrv.c new file mode 100644 index 00000000..1faff21b --- /dev/null +++ b/Tools/unix/zx/cpmdrv.c @@ -0,0 +1,275 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998,2003 John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file deals with drive-based functions. +*/ + +#include "cpmint.h" + +#ifdef WIN32 + +static char *drive_to_hostdrive(int cpm_drive) +{ + static char prefix[CPM_MAXPATH]; + char *lpfp; + DWORD dw; + + if (!redir_drive_prefix[cpm_drive]) return NULL; + dw = GetFullPathName(redir_drive_prefix[cpm_drive], sizeof(prefix), + prefix, &lpfp); + + if (!dw) return NULL; + if (prefix[1] == ':') /* If path starts with a drive, limit it */ + { /* to just that drive */ + prefix[2] = '/'; + prefix[3] = 0; + } + return prefix; +} +#endif + + +cpm_byte fcb_reset(void) +{ +#ifdef __MSDOS__ + bdos(0x0D, 0, 0); +#endif + + redir_l_drives = 0; + redir_cpmdrive = 0; /* A reset forces current drive to A: */ +/* redir_ro_drives = 0; Software write protect not revoked by func 0Dh. + * + * This does not follow true CP/M, but does match many 3rd-party replacements. + */ + return 0; +} + + +cpm_word fcb_drive (cpm_byte drv) +{ + if (redir_drive_prefix[drv][0]) + { + redir_cpmdrive = drv; + redir_log_drv(drv); + return 0; + } + else return 0x04FF; /* Drive doesn't exist */ +} + +cpm_byte fcb_getdrv(void) +{ + return redir_cpmdrive; +} + + +cpm_byte fcb_user (cpm_byte usr) +{ + if (usr != 0xFF) redir_cpmuser = usr % 16; + + redir_Msg("User: parameter %d returns %d\r\n", usr, redir_cpmuser); + + return redir_cpmuser; +} + + + +cpm_word fcb_logvec(void) +{ + return redir_l_drives; +} + + +cpm_word fcb_rovec(void) +{ + return redir_ro_drives; +} + + +cpm_word fcb_rodisk(void) +{ + cpm_word mask = 1; + + if (redir_cpmdrive) mask = mask << redir_cpmdrive; + + redir_ro_drives |= mask; + return 0; +} + + +cpm_word fcb_resro(cpm_word bitmap) +{ + redir_ro_drives &= ~bitmap; + + return 0; +} + + +cpm_word fcb_sync(cpm_byte flag) +{ +#ifdef WIN32 + return 0; +#else + sync(); return 0; /* Apparently some sync()s are void not int */ +#endif +} + + +cpm_word fcb_purge() +{ +#ifdef WIN32 + return 0; +#else + sync(); return 0; /* Apparently some sync()s are void not int */ +#endif +} + + +static cpm_byte exdpb[0x11] = { + 0x80, 0, /* 128 records/track */ + 0x04, 0x0F, /* 2k blocks */ + 0x00, /* 16k / extent */ + 0xFF, 0x0F, /* 4095 blocks */ + 0xFF, 0x03, /* 1024 dir entries */ + 0xFF, 0xFF, /* 16 directory blocks */ + 0x00, 0x80, /* Non-removable media */ + 0x00, 0x00, /* No system tracks */ + 0x02, 0x03 /* 512-byte sectors */ +}; + +cpm_word fcb_getdpb(cpm_byte *dpb) +{ +#ifdef WIN32 + DWORD spc, bps, fc, tc; + unsigned bsh, blm, psh, phm; + char *hostd = drive_to_hostdrive(redir_cpmdrive); + + if (!hostd) return 0x01FF; /* Can't select */ + + if (!GetDiskFreeSpace(hostd, &spc, &bps, &fc, &tc)) + return 0x01FF; /* Can't select */ + + /* Store total clusters */ + //if (tc > 0x10000L) tc = 0x10000L; + if (tc > 0xFFFFL) tc = 0xFFFFL; + + psh = 0; phm = 0; + + while (bps > 128) /* Get sector size */ + { + bps /= 2; + psh++; + phm = (phm << 1) | 1; + } + bsh = psh; blm = phm; + while (spc > 1) /* Get cluster size */ + { + spc /= 2; + bsh++; + blm = (blm << 1) | 1; + } + + + exdpb[2] = bsh; + exdpb[3] = blm; + exdpb[5] = tc & 0xFF; + exdpb[6] = tc >> 8; + + exdpb[15] = psh; + exdpb[16] = phm; +#else + struct statfs buf; + cpm_word nfiles; + + /* Get DPB for redir_cpmdrive. Currently just returns a dummy. */ + if (!statfs(redir_drive_prefix[redir_cpmdrive], &buf)) + { + /* Store correct directory entry count */ + + if (buf.f_files >= 0x10000L) nfiles = 0xFFFF; + else nfiles = buf.f_files; + + exdpb[7] = nfiles & 0xFF; + exdpb[8] = nfiles >> 8; + } +#endif + + memcpy(dpb, &exdpb, 0x11); + return 0x11; +} + + +/* Create an entirely bogus ALV + * TODO: Make it a bit better */ + +cpm_word fcb_getalv(cpm_byte *alv, cpm_word max) +{ + if (max > 1024) max = 1024; + + memset(alv, 0xFF, max / 2); + memset(alv + (max / 2), 0, max / 2); + + return max; +} + +/* Get disk free space */ + +cpm_word fcb_dfree (cpm_byte drive, cpm_byte *dma) +{ +#ifdef WIN32 + DWORD spc, bps, fc, tc; + DWORD freerec; + char *hostd = drive_to_hostdrive(drive); + + if (!hostd) return 0x01FF; + if (!hostd) return 0x01FF; /* Can't select */ + + if (!GetDiskFreeSpace(hostd, &spc, &bps, &fc, &tc)) + return 0x01FF; /* Can't select */ + + freerec = fc; /* Free clusters */ + freerec *= spc; /* Free sectors */ + freerec *= (bps / 128); /* Free CP/M records */ + + /* Limit to maximum CP/M drive size */ + if (freerec > 4194303L) freerec = 4194303L; + redir_wr24(dma, freerec); + +#else + struct statfs buf; + long dfree; + + if (!redir_drive_prefix[drive]) return 0x01FF; /* Can't select */ + + if (statfs(redir_drive_prefix[drive], &buf)) return 0x01FF; + + dfree = (buf.f_bavail * (buf.f_bsize / 128)); + + if (dfree < buf.f_bavail || /* Calculation has wrapped round */ + dfree > 4194303L) /* Bigger than max CP/M drive size */ + { + dfree = 4194303L; + } + + redir_wr24(dma, dfree); +#endif + return 0; +} + + + diff --git a/Tools/unix/zx/cpmglob.c b/Tools/unix/zx/cpmglob.c new file mode 100644 index 00000000..20306e09 --- /dev/null +++ b/Tools/unix/zx/cpmglob.c @@ -0,0 +1,579 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file implements those BDOS functions that use wildcard expansion. +*/ + +#include "cpmint.h" + +static cpm_byte *find_fcb; +static int find_n; +static int find_ext = 0; +static int find_xfcb = 0; +static int entryno; +static cpm_byte lastdma[0x80]; +static long lastsize; +static char target_name[CPM_MAXPATH]; + +static char upper(char c) +{ + if (islower(c)) return toupper(c); + return c; +} + +/* + * we need to handle case sensitive filesystems correctly. + * underlying files need to just work, irrespective if the files + * in the native filesystem are mixed, upper or lower. + * the naive code in the distributed zx will not work everywhere. + */ + +/* Does the string "s" match the CP/M FCB? */ +/* pattern[0-10] will become a CP/M name parsed from "s" if it matches. */ +/* If 1st byte of FCB is '?' then anything matches. */ + +static int cpm_match(char *s, cpm_byte *fcb, cpm_byte *pattern) +{ + int n, m; + char *dotpos; + + m = strlen(s); + + /* + * let's cook the filename into something that we can match against + * the fcb. we reject any that can't be valid cp/m filenames, + * normalizing case as we go. all this goes into 'pattern' + */ + for (n = 0; n < 11; n++) pattern[n] = ' '; + + /* The name must have 1 or 0 dots */ + dotpos = strchr(s, '.'); + if (!dotpos) { /* No dot. The name must be at most 8 characters */ + if (m > 8) return 0; + for (n = 0; n < m; n++) { + pattern[n] = upper(s[n]) & 0x7F; + } + } else { /* at least one dot */ + if (strchr(dotpos + 1, '.')) { /* More than 1 dot */ + return 0; + } + if (dotpos == s) { /* Dot right at the beginning */ + return 0; + } + if ((dotpos - s) > 8) { /* "name" > 8 characters */ + return 0; + } + if (strlen(dotpos + 1) > 3) { /* "type" > 3 characters */ + return 0; + } + for (n = 0; n < (dotpos - s); n++) { /* copy filename portion */ + pattern[n] = upper(s[n]) & 0x7F; + } + m = strlen(dotpos + 1); + for (n = 0; n < m; n++) { /* copy extention portion */ + pattern[n + 8] = upper(dotpos[n + 1]) & 0x7F; + } + } + + /* + * handle special case where fcb[0] == '?' or fcb[0] & 0x80 + * this is used to return a full directory list on bdos's + */ + if (((fcb[0] & 0x7F) == '?') || (fcb[0] & 0x80)) { + return 1; + } + + for (n = 0; n < 11; n++) { + if (fcb[n+1] == '?') continue; + if ((pattern[n] & 0x7F) != (fcb[n+1] & 0x7F)) { + return 0; + } + } + return 1; /* Success! */ +} + + +/* Get the next entry from the host's directory matching "fcb" */ + +static struct dirent * next_entry(DIR *dir, cpm_byte *fcb, cpm_byte *pattern, + struct stat *st) +{ + struct dirent *en; + int unsatisfied; + int drv = fcb[0] & 0x7F; + + if (drv == '?') drv = 0; + if (!drv) drv = redir_cpmdrive; + else drv--; + + for (unsatisfied = 1; unsatisfied; ) + { + /* 1. Get the next entry */ + + en = readdir(dir); + if (!en) return NULL; /* No next entry */ + ++entryno; /* 0 for 1st, 1 for 2nd, etc. */ + + /* 2. See if it matches. We do this first (in preference to + seeing if it's a subdirectory first) because it doesn't + require disc access */ + if (!cpm_match(en->d_name, fcb, pattern)) + { + continue; + } + /* 3. Stat it, & reject it if it's a directory */ + + strcpy(target_name, redir_drive_prefix[drv]); + strcat(target_name, en->d_name); + + if (stat(target_name, st)) + { + redir_Msg("Can't stat %s so omitting it.\n", target_name); + continue; /* Can't stat */ + } + //if (S_ISDIR(st->st_mode)) + if ((st->st_mode) & _S_IFDIR) + { + /* Searching for files only */ + if (fcb[0] != '?' && fcb[0] < 0x80) + { + continue; + } + } + unsatisfied = 0; + } + return en; +} + + + +void volume_label(int drv, cpm_byte *dma) +{ + struct stat st; + + memset(dma, 0x20, 12); /* Volume label */ + + /* Get label name */ + redir_get_label(drv, (char *)(dma + 1)); + /* [0x0c] = label byte + * [0x0d] = password byte (=0) + * [0x10-0x17] = password + * [0x18] = label create date + * [0x1c] = label update date + */ +#ifdef __MSDOS__ + dma[0x0c] = 0x21; /* Under DOS, only "update" */ + if (redir_drdos) dma[0x0c] |= 0x80; /* Under DRDOS, passwords allowed */ +#else + dma[0x0c] = 0x61; /* Label exists and time stamps allowed */ +#endif /* (update & access) */ + dma[0x0d] = 0; /* Label not passworded */ + dma[0x0f] = 0x80; /* Non-CP/M media */ + + if (stat(redir_drive_prefix[drv], &st)) + { + redir_Msg("stat() fails on '%s'\n", redir_drive_prefix[drv]); + return; + } + + redir_wr32(dma + 0x18, redir_cpmtime(st.st_atime)); + redir_wr32(dma + 0x1C, redir_cpmtime(st.st_mtime)); +} + + + +cpm_word redir_find(int n, cpm_byte *fcb, cpm_byte *dma) +{ + DIR *hostdir; + int drv, attrib; + long recs; + struct stat st; + struct dirent *de; + cpm_word rights; + + drv = (fcb[0] & 0x7F); + if (!drv || drv == '?') drv = redir_cpmdrive; + else drv--; + + if (find_xfcb) /* Return another extent */ + { + memcpy(dma, lastdma, 0x80); + dma[0] |= 0x10; /* XFCB */ + dma[0x0c] = dma[0x69]; /* Password mode */ + dma[0x0d] = 0x0A; /* Password decode byte */ + memset(dma + 0x10, '*', 7); + dma[0x17] = ' '; /* Encoded password */ + memcpy(lastdma, dma, 0x80); + + find_xfcb = 0; + return 0; + } + + if (find_ext) /* Return another extent */ + { + memcpy(dma, lastdma, 0x80); + dma[0x0c]++; + if (dma[0x0c] == 0x20) + { + dma[0x0c] = 0; + dma[0x0e]++; + } + lastsize -= 0x4000; + recs = (lastsize + 127) / 128; + dma[0x0f] = (recs > 127) ? 0x80 : (recs & 0x7F); + + if (lastsize <= 0x4000) find_ext = 0; + memcpy(lastdma, dma, 0x80); + + return 0; + } + + + memset(dma, 0, 128); /* Zap the buffer */ + + /* + If returning all entries, return a volume label. + */ + if ((fcb[0] & 0x7F) == '?') + { + if (!n) + { + volume_label(drv, dma); + return 0; + } + else --n; + } + + /* Note: This implies that opendir() works on a filename with a + trailing slash. It does under Linux, but that's the only assurance + I can give. + */ + + entryno = -1; + hostdir = opendir(redir_drive_prefix[drv]); + + if (!hostdir) + { + redir_Msg("opendir() fails on '%s'\n", redir_drive_prefix[drv]); + return 0xFF; + } + /* We have a handle to the directory. */ + while (n >= 0) + { + de = next_entry(hostdir, fcb, dma + 1, &st); + if (!de) return 0xFF; + --n; + } + /* Valid entry found & statted. dma+1 holds filename. */ + + dma[0] = redir_cpmuser; /* Uid always matches */ + dma[0x0c] = 0; /* Extent counter, low */ + dma[0x0d] = st.st_size & 0x7F; /* Last record byte count */ + dma[0x0e] = 0; /* Extent counter, high */ + +#ifdef __MSDOS__ + _dos_getfileattr(target_name, (unsigned int *)&attrib); + rights = redir_drdos_get_rights(target_name); + if (rights && ((fcb[0] & 0x7F) == '?')) find_xfcb = 1; +#else + attrib = 0; + rights = 0; +#endif + if (attrib & 1) dma[9] |= 0x80; + if (attrib & 4) dma[10] |= 0x80; + if (!(attrib & 0x20)) dma[11] |= 0x80; + + + +/* TODO: Under Unix, work out correct RO setting */ + + recs = (st.st_size + 127) / 128; + dma[0x0f] = (recs > 127) ? 0x80 : (recs & 0x7F); + dma[0x10] = 0x80; + //if (S_ISDIR(st.st_mode)) dma[0x10] |= 0x40; + if ((st.st_mode) & _S_IFDIR) {dma[0x10] |= 0x40;} + if (attrib & 2) dma[0x10] |= 0x20; + dma[0x10] |= ((entryno & 0x1FFF) >> 8); + dma[0x11] = dma[0x10]; + dma[0x12] = entryno & 0xFF; + + redir_wr32(dma + 0x16, st.st_mtime); /* Modification time. */ + /* TODO: It should be in DOS */ + /* format */ + /* TODO: At 0x1A, 1st cluster */ + redir_wr32(dma + 0x1C, st.st_size); /* True size */ + + if (rights) /* Store password mode. Don't return an XFCB. */ + { + dma[0x69] = redir_cpm_pwmode(rights); + memcpy(lastdma, dma, 0x80); + } + + dma[0x60] = 0x21; /* XFCB */ + redir_wr32(dma + 0x61, redir_cpmtime(st.st_atime)); + redir_wr32(dma + 0x65, redir_cpmtime(st.st_mtime)); + + closedir(hostdir); + + if (st.st_size > 0x4000 && (fcb[0x0C] == '?')) /* All extents? */ + { + lastsize = st.st_size; + find_ext = 1; + memcpy(lastdma, dma, 0x80); + } + return 0; +} + + +#ifdef DEBUG +#define SHOWNAME(func) \ + { \ + char fname[CPM_MAXPATH]; \ + redir_fcb2unix(fcb, fname); \ + redir_Msg(func "(\"%s\")\n", fname); \ + } + +#else + #define SHOWNAME(func) +#endif + +cpm_word fcb_find1 (cpm_byte *fcb, cpm_byte *dma) /* 0x11 */ +{ +#ifdef DEBUG + int rv; +#endif + SHOWNAME("fcb_find1") + + redir_log_fcb(fcb); + + find_n = 0; + find_fcb = fcb; + find_ext = 0; + find_xfcb = 0; +#ifdef DEBUG + rv = redir_find(find_n, fcb, dma); + + if (rv < 4) + { + redir_Msg("Ret: %-11.11s\n", dma + 1); + } + else redir_Msg("Ret: Fail\n"); + return rv; +#else + return redir_find(find_n, find_fcb, dma); +#endif +} + +/* We don't bother with the FCB parameter - it's undocmented, and + * programs that do know about it will just pass in the same parameter + * that they did to function 0x11 */ + +cpm_word fcb_find2 (cpm_byte *fcb, cpm_byte *dma) /* 0x12 */ +{ +#ifdef DEBUG + int rv; + + char fname[CPM_MAXPATH]; + redir_fcb2unix(find_fcb, fname); + redir_Msg("fcb_find2(\"%s\") no. %d\n", fname, find_n); +#endif + ++find_n; +#ifdef DEBUG + rv = redir_find(find_n, find_fcb, dma); + + if (rv < 4) + { + redir_Msg("Ret: %-11.11s\n", dma + 1); + } + else redir_Msg("Ret: Fail\n"); + return rv; +#else + return redir_find(find_n, find_fcb, dma); +#endif +} + +/* Under CP/M, unlinking works with wildcards */ + +cpm_word fcb_unlink(cpm_byte *fcb, cpm_byte *dma) +{ + DIR *hostdir; + int drv; + struct dirent *de; + struct stat st; + int handle = 0; + int unpasswd = 0; + char fname[CPM_MAXPATH]; + + SHOWNAME("fcb_unlink") + + if (fcb[5] & 0x80) unpasswd = 1; /* Remove password rather than file */ + + redir_log_fcb(fcb); + + drv = (fcb[0] & 0x7F); + if (!drv || drv == '?') drv = redir_cpmdrive; + else drv--; + + if (redir_ro_drv(drv)) return 0x02FF; /* Error: R/O drive */ + +#ifdef DEBUG + redir_fcb2unix(fcb, fname); + redir_Msg("fcb_unlink(\"%s\")\n", fname); +#endif + + /* Note: This implies that opendir() works on a filename with a + trailing slash. It does under Linux, but that's the only assurance + I can give. + */ + + hostdir = opendir(redir_drive_prefix[drv]); + + if (!hostdir) + { + redir_Msg("opendir() fails on '%s'\n", redir_drive_prefix[drv]); + return 0xFF; + } + /* We have a handle to the directory. */ + do + { + de = next_entry(hostdir, fcb, (cpm_byte *)fname, &st); + if (de) + { + strcpy(target_name, redir_drive_prefix[drv]); + strcat(target_name, de->d_name); + redir_Msg("Deleting %s\n", de->d_name); + if (unpasswd) + { +#ifdef __MSDOS__ + if (redir_drdos) + { + handle = redir_drdos_put_rights (target_name, dma, 0); + } + else handle = 0; +#endif + } + else if (fcb[0] & 0x80) + { + handle = rmdir(target_name); + if (handle && redir_password_error()) + { + redir_password_append(target_name, dma); + handle = rmdir(target_name); + } + } + else + { + handle = unlink(target_name); + if (handle && redir_password_error()) + { + redir_password_append(target_name, dma); + handle = unlink(target_name); + } + } + + if (handle) de = NULL; /* Delete failed */ + } + } + while (de != NULL); + if (handle) + { + redir_Msg("Ret: -1\n"); + closedir(hostdir); + return 0xFF; + } + redir_Msg("Ret: 0\n"); + closedir(hostdir); + return 0; +} + + + + + +#ifdef __MSDOS__ +cpm_word redir_get_label(cpm_byte drv, char *pattern) +{ + char strs[10]; + struct ffblk fblk; + int done; + char *s; + int n; + + /* We need the drive prefix to be of the form "C:\etc..." */ + + memset(pattern, ' ', 11); + if (!redir_drive_prefix[drv][0] || redir_drive_prefix[drv][1] != ':') + return 0; + + sprintf(strs,"%c:/*.*", redir_drive_prefix[drv][0]); + + done = findfirst(strs, &fblk, FA_LABEL); + while (!done) + { + if ((fblk.ff_attrib & FA_LABEL) && + !(fblk.ff_attrib & (FA_SYSTEM | FA_HIDDEN))) + { + s = strchr(fblk.ff_name, '/'); + if (!s) s = strchr(fblk.ff_name, '\\'); + if (!s) s = strchr(fblk.ff_name, ':'); + if (!s) s = fblk.ff_name; + for (n = 0; n < 11; n++) + { + if (!(*s)) break; + if (*s == '.') + { + n = 7; + ++s; + continue; + } + pattern[n] = upper(*s); + ++s; + } + return 1; + } + done = findnext(&fblk); + } + return 0; +} +#else +cpm_word redir_get_label(cpm_byte drv, char *pattern) +{ + char *dname; + int l, n; + + memset(pattern, ' ', 11); + + dname = strrchr(redir_drive_prefix[drv], '/'); + if (dname) + { + ++dname; + l = strlen(dname); + if (l > 11) l = 11; + for (n = 0; n < l; n++) pattern[n] = upper(dname[l]); + } + else + { + pattern[0] = '.'; + } + return 0; +} + + + +#endif diff --git a/Tools/unix/zx/cpmint.h b/Tools/unix/zx/cpmint.h new file mode 100644 index 00000000..f0ec4237 --- /dev/null +++ b/Tools/unix/zx/cpmint.h @@ -0,0 +1,223 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds internal declarations for the library. +*/ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_DIRENT_H +# include +#else +#ifdef __WATCOMC__ +# include +# include +#else +# include "dirent.h" +#endif +#endif +#ifdef HAVE_NDIR_H +# include +#endif +#ifdef HAVE_SYS_DIR_H +# include +#endif +#ifdef HAVE_SYS_NDIR_H +# include +#endif +#ifdef HAVE_WINDOWS_H +# include +#endif +#ifdef HAVE_WINNT_H +# include +#endif +#ifdef HAVE_SYS_VFS_H +# include +#endif +#ifdef HAVE_UTIME_H +# include +#endif +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + + +#ifdef __MSDOS__ + #include + #include + #include + #ifdef __GO32__ + #include + #include + #include + #endif +#endif + +#define CASE_SENSITIVE_FILESYSTEM 0 + + +#include "cpmredir.h" + +typedef unsigned long dword; /* Must be at least 32 bits, and + >= sizeof(int) */ +#ifdef CPMDEF + #define EXT + #define INIT(x) =x +#else + #define EXT extern + #define INIT(x) +#endif + +/* The 16 directories to which the 16 CP/M drives are mapped */ + +EXT char redir_drive_prefix[16][CPM_MAXPATH]; + +/* Current drive and user */ + +EXT int redir_cpmdrive; +EXT int redir_cpmuser; + +/* Length of 1 read/write operation, bytes */ + +EXT int redir_rec_len INIT(128); + +/* Same, but in 128-byte records */ +EXT int redir_rec_multi INIT(1); + +/* Using a DRDOS system? */ +EXT int redir_drdos INIT(0); + +/* Default password */ +#ifdef __MSDOS__ +EXT char redir_passwd[8] INIT(""); +#endif + +EXT cpm_word redir_l_drives INIT(0); +EXT cpm_word redir_ro_drives INIT(0); + +#undef EXT +#undef INIT + + + +/* Convert FCB to a Unix filename, returning 1 if it's ambiguous */ +int redir_fcb2unix(cpm_byte *fcb, char *fname); + +/* Open FCB, set file attributes */ +int redir_ofile(cpm_byte * fcb, char *s); + +/* Check that the FCB we have is valid */ +int redir_verify_fcb(cpm_byte *fcb); + +#ifndef O_BINARY /* Necessary in DOS, not present in Linux */ +#define O_BINARY 0 +#endif + +/* Facilities for debug tracing */ + + +long zxlseek(int fd, long offset, int wh); + +#ifdef DEBUG + void redir_Msg(char *s, ...); + void redir_showfcb(cpm_byte *fcb); +#else + /* Warning: This is a GCC extension */ + #define redir_Msg(x, ...) + #define redir_showfcb(x) +#endif + + + +/* Get the "sequential access" file pointer out of an FCB */ + +long redir_get_fcb_pos(cpm_byte *fcb); + +/* Write "sequential access" pointer to FCB */ + +void redir_put_fcb_pos(cpm_byte *fcb, long npos); + +/* Convert time_t to CP/M day count/hours/minutes */ +dword redir_cpmtime(time_t t); +/* And back */ +time_t redir_unixtime(cpm_byte *c); + + +/* Functions to access 24-bit & 32-bit words in memory. These are always + little-endian. */ + +void redir_wr24(cpm_byte *addr, dword v); +void redir_wr32(cpm_byte *addr, dword v); +dword redir_rd24(cpm_byte *addr); +dword redir_rd32(cpm_byte *addr); + +/* If you have 64-bit file handles, you'll need to write separate wrhandle() + and rdhandle() routines */ +#define redir_wrhandle redir_wr32 +#define redir_rdhandle redir_rd32 + +/* Mark a drive as logged in */ + +void redir_log_drv(cpm_byte drv); +void redir_log_fcb(cpm_byte *fcb); + +/* Check if a drive is software read-only */ + +int redir_ro_drv(cpm_byte drv); +int redir_ro_fcb(cpm_byte *fcb); + +/* Translate errno to a CP/M error */ + +cpm_word redir_xlt_err(void); + +/* Get disc label */ +cpm_word redir_get_label(cpm_byte drv, char *pattern); + + +/* DRDOS set/get access rights - no-ops under MSDOS and Unix: + * + * CP/M password mode -> DRDOS password mode */ +cpm_word redir_drdos_pwmode(cpm_byte b); + +/* DRDOS password mode to CP/M password mode */ +cpm_byte redir_cpm_pwmode(cpm_word w); + +/* Get DRDOS access rights for a file */ +cpm_word redir_drdos_get_rights(char *path); + +/* Set DRDOS access rights and/or password */ +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights); + +/* Was the last error caused by invalid password? */ +cpm_word redir_password_error(void); + +/* Append password to filename (FILE.TYP -> FILE.TYP;PASSWORD) */ +void redir_password_append(char *s, cpm_byte *dma); + diff --git a/Tools/unix/zx/cpmparse.c b/Tools/unix/zx/cpmparse.c new file mode 100644 index 00000000..4c709ec1 --- /dev/null +++ b/Tools/unix/zx/cpmparse.c @@ -0,0 +1,126 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file parses filenames to FCBs. +*/ + +#include "cpmint.h" + +#define is_num(c) ((c >= '0') && (c <= '9')) + +static int parse_drive_user(char *txt, cpm_byte *fcb) +{ + char uid[4], drvid[4]; + int up, dp; + + for (up = dp = 0; *txt != ':'; ++txt) + { + if (is_num (*txt)) uid [up++] = *txt; + if (isalpha(*txt)) drvid[dp++] = *txt; + if (!is_num(*txt) && !isalpha(*txt)) return -1; + } + uid[up] = 0; drvid[dp] = 0; + + if (dp > 1) return -1; /* Invalid driveletter */ + if (up > 2) return -1; /* Invalid uid */ + + fcb[0x0d] = atoi(uid) + 1; if (fcb[0x0d] > 16) return -1; + + if (islower(drvid[0])) drvid[0] = toupper(drvid[0]); + + if (drvid[0] < 'A' || drvid[0] > 'P') return -1; + + fcb[0] = drvid[0] - '@'; + return 0; +} + + + +cpm_word fcb_parse(char *txt, cpm_byte *fcb) +{ + int nl = 0, tl = 0, pl = 0, phase = 0; + char *ntxt, ch; + + memset(fcb, 0, 0x24); + + if (txt[1] == ':' || txt[2] == ':' || txt[3] == ':') + { + if (parse_drive_user(txt, fcb)) return 0xFFFF; + /* Move past the colon */ + ntxt = strchr(txt, ':') + 1; + } + else ntxt = txt; + while (phase < 3) + { + ch = *ntxt; + if (islower(ch)) ch = toupper(ch); + + switch(ch) + { + case 0: + case '\r': /* EOL */ + phase = 4; + break; + + case '.': /* file.typ */ + if (!phase) ++phase; + else phase = 3; + break; + + case ';': /* Password */ + if (phase < 2) phase = 2; + else phase = 3; + break; + + case '[': case ']': case '=': case 9: case ' ': + case '>': case '<': case ':': case ',': case '/': + case '|': /* Terminator */ + phase = 3; + + default: + switch(phase) + { + case 0: + if (nl >= 8) return 0xFFFF; + fcb[++nl] = ch; + break; + + case 1: + if (tl >= 3) return 0xFFFF; + fcb[tl + 9] = ch; + ++tl; + break; + + case 2: + if (pl >= 8) return 0xFFFF; + fcb[pl + 0x10] = ch; + ++pl; + break; + } + break; + } + } + if (!nl) return 0xFFFF; + + fcb[0x1A] = pl; + + if (phase == 4) return 0; + + return ntxt - txt; +} diff --git a/Tools/unix/zx/cpmredir.c b/Tools/unix/zx/cpmredir.c new file mode 100644 index 00000000..4fd5f9ca --- /dev/null +++ b/Tools/unix/zx/cpmredir.c @@ -0,0 +1,931 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* This file handles actual reading and writing */ + +#define CPMDEF +#include "cpmint.h" + +#ifdef DEBUG +#define SHOWNAME(func) \ + { \ + char fname[CPM_MAXPATH]; \ + redir_fcb2unix(fcb, fname); \ + redir_Msg(func "(\"%s\")\n", fname); \ + } + +#else + #define SHOWNAME(func) +#endif + + +/* DISK BDOS FUNCTIONS */ + +/* General treatment: + * + * We use the "disk block number" fields in the FCB to store our file handle; + * this is a similar trick to that used by DOSPLUS, which stores its cluster + * number in there. It works if: + * + * a) sizeof(int) <= 8 bytes (64 bits). If it's more, this needs rewriting + * to use a hash table; + * b) the program never touches these bytes. Practically no CP/M program does. + * + * We store a "magic number" (0x00FD) in the first two bytes of this field, and + * if the number has been changed then we abort. + * + * nb: Since I wrote ZXCC, I have found that DOSPLUS uses 0x8080 as a magic + * number [well, actually this is an oversimplification, but a hypothetical + * program written against DOSPLUS would work with 0x8080]. Perhaps 0x8080 + * should be used instead. + * + * Format of the field: + * + * [--2 bytes--] magic number + * [--8 bytes--] file handle. 8 bytes reserved but only 4 currently used. + * [--2 bytes--] reserved. + * [--4 bytes--] file length. + */ +#define MAGIC_OFFSET 0x10 +#define HANDLE_OFFSET 0x12 +#define LENGTH_OFFSET 0x1C + +cpm_word fcb_open(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; + int handle; + int drv, l; + char *s; + DIR *dir; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + redir_log_fcb(fcb); + + drv = fcb[0] & 0x7F; + if (!drv) drv = redir_cpmdrive; else --drv; + + if (fcb[0] & 0x80) /* Open directory */ + { + if (fcb[0x0C]) return 0x0BFF; /* Can't assign "floating" dir */ + + if (!memcmp(fcb + 1, ". ", 11)) + { + return 0; /* Opening "." */ + } + if (!memcmp(fcb + 1, ".. ", 11)) + { + l = strlen(redir_drive_prefix[drv]) - 1; + s = redir_drive_prefix[drv]; + --l; + while (l > 0) + { + if (s[l] == '/') break; +#ifdef __MSDOS__ + if (s[l] == '\\') break; + if (s[l] == ':') break; +#endif + --l; + } +#ifdef __MSDOS__ + if (l < 2) return 0; /* "C:" */ +#else + if (l <= 0) return 0; /* "/" */ +#endif + ++l; + s[l] = 0; + return 0; + } +/* Opening some other directory */ + + dir = opendir(fname); + if (!dir) return 0xFF; /* Not a directory */ + closedir(dir); + strcpy(redir_drive_prefix[drv], fname); + strcat(redir_drive_prefix[drv], "/"); + return 0; + } + + /* Note: Some programs (MAC is an example) don't close a file + * if they opened it just to do reading. MAC then reopens the + * file (which rewinds it); this causes FCB leaks under some + * DOS-based emulators */ + + handle = redir_ofile(fcb, fname); + redir_Msg("fcb_open(\"%s\")\r\n", fname); + if (handle < 0 && redir_password_error()) + { + redir_Msg("1st chance open failed on %s\r\n", fname); + redir_password_append(fname, dma); + redir_Msg("Trying with %s\r\n", fname); + handle = redir_ofile(fcb, fname); + } + + + if (handle == -1) + { + redir_Msg("Ret: -1\n"); + if (redir_password_error()) return 0x7FF; + return 0xFF; + } + fcb[MAGIC_OFFSET ] = 0xFD; /* "Magic number" */ + fcb[MAGIC_OFFSET + 1] = 0x00; + +/* TODO: Should the magic number perhaps be 0x8080, as in DOSPLUS? */ + + redir_wrhandle(fcb + HANDLE_OFFSET, handle); + + redir_put_fcb_pos(fcb, fcb[0x0C] * 16384); + /* (v1.01) "seek" to beginning of extent, not file. + * This is necessary for the awful I/O code + * in LINK-80 to work + */ + + /* Get the file length */ + redir_wr32(fcb + 0x1C, zxlseek(handle, 0, SEEK_END)); + zxlseek(handle, 0, SEEK_SET); + + /* Set the last record byte count */ + if (fcb[0x20] == 0xFF) fcb[0x20] = fcb[0x1C] & 0x7F; + + redir_Msg("Ret: 0\n"); + + return 0; +} + + +cpm_word fcb_close(cpm_byte *fcb) +{ + int handle, drv; + + SHOWNAME("fcb_close") + + if ((handle = redir_verify_fcb(fcb)) < 0) return -1; + redir_Msg(" (at %lx)\n", zxlseek(handle, 0, SEEK_CUR)); + + if (fcb[0] & 0x80) /* Close directory */ + { + drv = fcb[0] & 0x7F; + if (!drv) drv = redir_cpmdrive; else drv--; +#ifdef __MSDOS__ + strcpy(redir_drive_prefix[drv] + 1, ":/"); +#else + strcpy(redir_drive_prefix[drv], "/"); +#endif + return 0; + } + + if (fcb[5] & 0x80) /* CP/M 3: Flush rather than close */ + { +#ifndef WIN32 + sync(); +#endif + return 0; + } + +#ifdef WIN32 + { + BOOL b; + redir_Msg(">CloseHandle() Handle=%lu\n", handle); + b = CloseHandle((HANDLE)handle); + redir_Msg("80h, let it be 80h + +*/ + + +cpm_word fcb_read(cpm_byte *fcb, cpm_byte *dma) +{ + int handle; + int rv, n, rd_len; + long npos; + + SHOWNAME("fcb_read") + + if ((handle = redir_verify_fcb(fcb)) < 0) return 9; /* Invalid FCB */ + + /* The program may have mucked about with the counters, so + * do an lseek() to where it should be. */ + + npos = redir_get_fcb_pos(fcb); + zxlseek(handle, npos, SEEK_SET); + redir_Msg(" (from %lx)\n", zxlseek(handle, 0, SEEK_CUR)); + + /* Read in the required amount */ + +#ifdef WIN32 + { + BOOL b; + redir_Msg(">ReadFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = ReadFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("WriteFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = WriteFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("CreateFile([CREATE_ALWAYS]) Name='%s'\n", fname); + handle = (int)CreateFile(fname, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + redir_Msg("ReadFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = ReadFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("WriteFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = WriteFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("WriteFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, zerorec, rl); + b = WriteFile((HANDLE)handle, zerorec, rl, (unsigned long *)(&rv), NULL); + redir_Msg("= 0) len += rv; + + if (rv < rl) + { + redir_wr32(fcb + LENGTH_OFFSET, len); + return redir_xlt_err(); + } + } + redir_wr32(fcb + LENGTH_OFFSET, offs); + + return fcb_randwr(fcb, dma); +} + +cpm_word fcb_tell(cpm_byte *fcb) +{ + int handle; + off_t rv; + + SHOWNAME("fcb_tell") + + if ((handle = redir_verify_fcb(fcb)) < 0) return 9; /* Invalid FCB */ + + rv = zxlseek(handle, 0, SEEK_CUR); + + if (rv < 0) return 0xFF; + + rv = rv >> 7; + fcb[0x21] = rv & 0xFF; + fcb[0x22] = (rv >> 8) & 0xFF; + fcb[0x23] = (rv >> 16) & 0xFF; + return 0; +} + + +cpm_word fcb_stat(cpm_byte *fcb) +{ + char fname[CPM_MAXPATH]; + struct stat st; + int rv; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + rv = stat(fname, &st); + + redir_Msg("fcb_stat(\"%s\") fcb=%x\n", fname, (int)fcb); + if (rv < 0) + { + redir_Msg("ret: -1\n"); + return 0xFF; + } + + redir_wr24(fcb + 0x21, (st.st_size + 127) / 128); + + redir_Msg("ret: 0"); + return 0; +} + + +cpm_word fcb_multirec(cpm_byte rc) +{ + if (rc < 1 || rc > 128) return 0xFF; + + redir_rec_multi = rc; + redir_rec_len = 128 * rc; + redir_Msg("Set read/write to %d bytes\n", redir_rec_len); + return 0; +} + + +cpm_word fcb_date(cpm_byte *fcb) +{ + char fname[CPM_MAXPATH]; + struct stat st; + int rv; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + rv = stat(fname, &st); + + redir_Msg("fcb_stat(\"%s\")\n", fname); + if (rv < 0) return 0xFF; + + redir_wr32(fcb + 0x18, redir_cpmtime(st.st_atime)); + redir_wr32(fcb + 0x1C, redir_cpmtime(st.st_ctime)); + + fcb[0x0C] = redir_cpm_pwmode(redir_drdos_get_rights(fname)); + return 0; +} + + + +cpm_word fcb_trunc(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; + dword offs = redir_rd24(fcb + 0x21) * 128; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); +#ifdef WIN32 + (void)offs; + return 0x06FF; /* Simply not implemented */ +#else + if (truncate(fname, offs)) + { + if (redir_password_error()) + { + redir_password_append(fname, dma); + if (!truncate(fname, offs)) return 0; + } + return redir_xlt_err(); + } + return 0; +#endif +} + + +cpm_word fcb_sdate(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; +#ifdef WIN32 + /* TODO: Use SetFileTime() here */ + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); +#else + struct utimbuf buf; + + buf.actime = redir_unixtime(dma); + buf.modtime = redir_unixtime(dma + 4); + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); + + if (utime(fname, &buf)) + { + if (redir_password_error()) + { + redir_password_append(fname, dma); + if (!utime(fname, &buf)) return 0; + } + return redir_xlt_err(); + } +#endif + return 0; +} + + + +cpm_word fcb_chmod(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; + struct stat st; + int handle, wlen, omode; + long offs, newoffs; + cpm_byte zero[128]; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); + + if (stat(fname, &st)) return redir_xlt_err(); + +#ifdef __MSDOS__ + omode = 0; + if (fcb[9] & 0x80) omode |= 1; + if (fcb[10] & 0x80) omode |= 4; + if (!(fcb[11] & 0x80)) omode |= 0x20; + + if (_chmod(fname, 1, omode) < 0) + { + if (redir_password_error()) + { + redir_password_append(fname, dma); + if (_chmod(fname, 1, omode) >= 0) return 0; + } + return redir_xlt_err(); + } +#elif defined (WIN32) + omode = 0; + + if (fcb[9] & 0x80) omode |= FILE_ATTRIBUTE_READONLY; + if (fcb[10] & 0x80) omode |= FILE_ATTRIBUTE_SYSTEM; + if (!(fcb[11] & 0x80)) omode |= FILE_ATTRIBUTE_ARCHIVE; + + if (!omode) omode = FILE_ATTRIBUTE_NORMAL; + + SetFileAttributes(fname, omode); +#else + omode = st.st_mode; + if (fcb[9] & 0x80) /* Read-only */ + { + st.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + } + else st.st_mode |= S_IWUSR; + + if (omode != st.st_mode) + { + if (chmod(fname, st.st_mode)) return redir_xlt_err(); + } + +#endif + + if (fcb[6] & 0x80) /* Set exact size */ + { + if (stat(fname, &st)) return redir_xlt_err(); + + handle = open(fname, O_RDWR | O_BINARY); + if (handle < 0) return redir_xlt_err(); + + newoffs = offs = ((st.st_size + 127) / 128) * 128; + if (fcb[0x20] & 0x7F) + { + newoffs -= (0x80 - (fcb[0x20] & 0x7F)); + } + if (newoffs == st.st_size) + { + ; /* Nothing to do! */ + } + else if (newoffs < st.st_size) + { +#ifndef WIN32 /* XXX Do this somehow in Win32 */ + if (ftruncate(handle, newoffs)) + { + close(handle); + return redir_xlt_err(); + } +#endif + } + else while (newoffs > st.st_size) + { + wlen = newoffs - st.st_size; + if (wlen > 0x80) wlen = 0x80; + memset(zero, 0x1A, sizeof(zero)); + if (write(handle, zero, wlen) < wlen) + { + close(handle); + return redir_xlt_err(); + } + st.st_size += wlen; + } + close(handle); + } + return 0; +} + + + + +cpm_word fcb_setpwd(cpm_byte *fcb, cpm_byte *dma) +{ +#ifdef __MSDOS__ + char fname[CPM_MAXPATH]; + cpm_word rv; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); + + rv = redir_drdos_put_rights(fname, dma, redir_drdos_pwmode(fcb[0x0c])); + if (rv || !(fcb[0x0c] & 1)) return rv; + return redir_drdos_put_rights(fname, dma, redir_drdos_pwmode(fcb[0x0c]) | 0x8000); +#else + return 0xFF; /* Unix doesn't do this */ +#endif +} + + +cpm_word fcb_getlbl(cpm_byte drv) +{ + redir_Msg("fcb_getlbl()\r\n"); +#ifdef __MSDOS__ + if (redir_drdos) return 0xA1; /* Supports passwords & Update stamps */ + return 0x21; /* Update stamps only */ +#else + return 0x61; /* Update & Access stamps */ +#endif +} + +cpm_word fcb_setlbl(cpm_byte *fcb, cpm_byte *dma) +{ +/* I am not letting CP/M fiddle with the host's FS settings - even if they + * could be altered, which they mostly can't. */ + + return 0x03FF; +} + + + +cpm_word fcb_defpwd(cpm_byte *pwd) +{ +#ifdef __MSDOS__ + union REGS r; + struct SREGS s; + + if (pwd[0] == 0 || pwd[0] == ' ') + { + redir_passwd[0] = 0; + } + else memcpy(redir_passwd, pwd, 8); + if (redir_drdos) + { + dosmemput(pwd, 8, __tb); + r.w.ax = 0x4454; + r.w.dx = __tb & 0x0F; + s.ds = __tb >> 4; + intdosx(&r, &r, &s); + } + +#endif + return 0; +} + + diff --git a/Tools/unix/zx/cpmredir.h b/Tools/unix/zx/cpmredir.h new file mode 100644 index 00000000..2584e239 --- /dev/null +++ b/Tools/unix/zx/cpmredir.h @@ -0,0 +1,151 @@ +/* + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds the public interface to CPMREDIR. +*/ + +#ifndef CPMREDIR_H_INCLUDED + +#define CPMREDIR_H_INCLUDED 16-11-1998 + +/* The "cpm_byte" must be exactly 8 bits. + The "cpm_word" must be exactly 16 bits. */ + +typedef unsigned char cpm_byte; +typedef unsigned short cpm_word; + +/* Maximum length of a directory path */ +#ifdef _POSIX_PATH_MAX + #define CPM_MAXPATH _POSIX_PATH_MAX +#else + #ifdef _MAX_PATH + #define CPM_MAXPATH _MAX_PATH + #else + #define CPM_MAXPATH 260 + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialise this library. Call this function first. + * + * Returns 0 if failed to initialise. + */ +int fcb_init(void); + +/* Deinitialise the library. */ + +void fcb_deinit(void); + +/* Translate a name from the host FS to a CP/M name. This will (if necessary) + * create a mapping between a CP/M drive and a host directory path. + * + * CP/M drives A: to O: can be mapped in this way. P: is always the current + * drive. + * + */ + +void xlt_name(char *localname, char *cpmname); + +/* It is sometimes convenient to set some fixed mappings. This will create + * a mapping for a given directory. + * Pass drive = -1 for "first available", or 0-15 for A: to P: + * Returns 1 if OK, 0 if requested drive not available. + * + * NB: It is important that the localname should have a trailing + * directory separator! + */ + +int xlt_map(int drive, char *localdir); + +/* + * This revokes a mapping. No check is made whether CP/M has files open + * on the drive or not. + */ + +int xlt_umap(int drive); + +/* Find out if a drive is mapped, and if so to what directory */ + +char *xlt_getcwd(int drive); + + +/* BDOS functions. Eventually this should handle all disc-related BDOS + * functions. + * + * I am assuming that your emulator has the CP/M RAM in its normal address + * space, accessible as a range 0-64k. If this is not the case + * (eg: you are emulating banked memory, or using a segmented architecture) + * you will have to use "copy in and copy out" techniques. The "fcb" area + * must be 36 bytes long; the "dma" area should be 128 * the value set + * in fcb_multirec() [default is 1, so 128 bytes]. + * + */ + +cpm_byte fcb_reset (void); /* 0x0D */ +cpm_word fcb_drive (cpm_byte drv); /* 0x0E */ +cpm_word fcb_open (cpm_byte *fcb, cpm_byte *dma); /* 0x0F */ +cpm_word fcb_close (cpm_byte *fcb); /* 0x10 */ +cpm_word fcb_find1 (cpm_byte *fcb, cpm_byte *dma); /* 0x11 */ +cpm_word fcb_find2 (cpm_byte *fcb, cpm_byte *dma); /* 0x12 */ +cpm_word fcb_unlink(cpm_byte *fcb, cpm_byte *dma); /* 0x13 */ +cpm_word fcb_read (cpm_byte *fcb, cpm_byte *dma); /* 0x14 */ +cpm_word fcb_write (cpm_byte *fcb, cpm_byte *dma); /* 0x15 */ +cpm_word fcb_creat (cpm_byte *fcb, cpm_byte *dma); /* 0x16 */ +cpm_word fcb_rename(cpm_byte *fcb, cpm_byte *dma); /* 0x17 */ +cpm_word fcb_logvec(void); /* 0x18 */ +cpm_byte fcb_getdrv(void); /* 0x19 */ +/* DMA is a parameter to routines, not a separate call */ +cpm_word fcb_getalv(cpm_byte *alv, cpm_word max); /* 0x1B */ +/* Get alloc vector: caller must provide space and say how big it is. */ +cpm_word fcb_rodisk(void); /* 0x1C */ +cpm_word fcb_rovec (void); /* 0x1D */ +cpm_word fcb_chmod (cpm_byte *fcb, cpm_byte *dma); /* 0x1E */ +cpm_word fcb_getdpb(cpm_byte *dpb); /* 0x1F */ +cpm_byte fcb_user (cpm_byte usr); /* 0x20 */ +cpm_word fcb_randrd(cpm_byte *fcb, cpm_byte *dma); /* 0x21 */ +cpm_word fcb_randwr(cpm_byte *fcb, cpm_byte *dma); /* 0x22 */ +cpm_word fcb_stat (cpm_byte *fcb); /* 0x23 */ +cpm_word fcb_tell (cpm_byte *fcb); /* 0x24 */ +cpm_word fcb_resro (cpm_word bitmap); /* 0x25 */ +/* Access Drives and Free Drives are not supported. */ +cpm_word fcb_randwz(cpm_byte *fcb, cpm_byte *dma); /* 0x28 */ +/* Record locking calls not supported (though they could be) */ +cpm_word fcb_multirec(cpm_byte rc); /* 0x2C */ +/* Set hardware error action must be done by caller */ +cpm_word fcb_dfree (cpm_byte drive, cpm_byte *dma);/* 0x2E */ +cpm_word fcb_sync (cpm_byte flag); /* 0x30 */ +cpm_word fcb_purge (void); /* 0x62 */ +cpm_word fcb_trunc (cpm_byte *fcb, cpm_byte *dma); /* 0x63 */ +cpm_word fcb_setlbl(cpm_byte *fcb, cpm_byte *dma); /* 0x64 */ +cpm_word fcb_getlbl(cpm_byte drive); /* 0x65 */ +cpm_word fcb_date (cpm_byte *fcb); /* 0x66 */ +cpm_word fcb_setpwd(cpm_byte *fcb, cpm_byte *dma); /* 0x67 */ +cpm_word fcb_defpwd(cpm_byte *pwd); /* 0x6A */ +cpm_word fcb_sdate (cpm_byte *fcb, cpm_byte *dma); /* 0x74 */ +cpm_word fcb_parse (char *txt, cpm_byte *fcb); /* 0x98 */ + +/* fcb_parse returns length of filename parsed, 0 if EOL, 0xFFFF if error */ +#ifdef __cplusplus +} +#endif + + +#endif /* def CPMREDIR_H_INCLUDED */ diff --git a/Tools/unix/zx/dirent.c b/Tools/unix/zx/dirent.c new file mode 100644 index 00000000..87cf85a9 --- /dev/null +++ b/Tools/unix/zx/dirent.c @@ -0,0 +1,149 @@ +/* + + Implementation of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003 and July 2012. + Rights: See end of file. + +*/ + +#include "dirent.h" +#include +#include /* _findfirst and _findnext set errno iff they return -1 */ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +//typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */ +typedef long handle_type; /* C99's intptr_t not sufficiently portable */ + +struct DIR +{ + handle_type handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* null-terminated char string */ +}; + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if(name && name[0]) + { + size_t base_length = strlen(name); + const char *all = /* search pattern must end with suitable wildcard */ + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if((dir = (DIR *) malloc(sizeof *dir)) != 0 && + (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) + { + strcat(strcpy(dir->name, name), all); + + if((dir->handle = + (handle_type) _findfirst(dir->name, &dir->info)) != -1) + { + dir->result.d_name = 0; + } + else /* rollback */ + { + free(dir->name); + free(dir); + dir = 0; + } + } + else /* rollback */ + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else + { + errno = EINVAL; + } + + return dir; +} + +int closedir(DIR *dir) +{ + int result = -1; + + if(dir) + { + if(dir->handle != -1) + { + result = _findclose(dir->handle); + } + + free(dir->name); + free(dir); + } + + if(result == -1) /* map all errors to EBADF */ + { + errno = EBADF; + } + + return result; +} + +struct dirent *readdir(DIR *dir) +{ + struct dirent *result = 0; + + if(dir && dir->handle != -1) + { + if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) + { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else + { + errno = EBADF; + } + + return result; +} + +void rewinddir(DIR *dir) +{ + if(dir && dir->handle != -1) + { + _findclose(dir->handle); + dir->handle = (handle_type) _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else + { + errno = EBADF; + } +} + +#ifdef __cplusplus +} +#endif + +/* + + Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ diff --git a/Tools/unix/zx/dirent.h b/Tools/unix/zx/dirent.h new file mode 100644 index 00000000..bbbfce52 --- /dev/null +++ b/Tools/unix/zx/dirent.h @@ -0,0 +1,50 @@ +#ifndef DIRENT_INCLUDED +#define DIRENT_INCLUDED + +/* + + Declaration of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003. + Rights: See end of file. + +*/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct DIR DIR; + +struct dirent +{ + char *d_name; +}; + +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); + +/* + + Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Tools/unix/zx/drdos.c b/Tools/unix/zx/drdos.c new file mode 100644 index 00000000..97bd583c --- /dev/null +++ b/Tools/unix/zx/drdos.c @@ -0,0 +1,236 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds DRDOS-specific password code. +*/ + +#include "cpmint.h" + +cpm_word redir_drdos_pwmode(cpm_byte b) +{ + cpm_word mode = 0; + + if (b & 0x80) mode |= 0xddd; + if (b & 0x40) mode |= 0x555; + if (b & 0x20) mode |= 0x111; + + return mode; +} + +cpm_byte redir_cpm_pwmode(cpm_word w) +{ + cpm_byte mode = 0; + + if (w & 0x8) mode |= 0x80; + if (w & 0x4) mode |= 0x40; + if (w & 0x1) mode |= 0x20; + + return mode; +} + +#ifdef __MSDOS__ +#ifdef __GO32__ /* The GO32 extender doesn't understand DRDOS password + * functions, so these are done with __dpmi_int() rather + * than intdos() */ + +cpm_word redir_drdos_get_rights(char *path) +{ + __dpmi_regs r; + + if (!redir_drdos) return 0; + + redir_Msg("Rights for file %s: \n\r", path); + + dosmemput(path, strlen(path) + 1, __tb); + r.x.ax = 0x4302; + r.x.dx = __tb & 0x0F; + r.x.ds = (__tb) >> 4; + + __dpmi_int(0x21, &r); + + redir_Msg(" %04x \n\r", r.x.cx); + + if (r.x.flags & 1) return 0; + return r.x.cx; +} + + +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights) +{ + __dpmi_regs r; + + if (!redir_drdos) return 0; + + redir_Msg("Put rights for file %s: %04x %-8.8s %-8.8s\n\r", path, rights, dma, dma + 8); + + dosmemput(dma+8, 8, __tb); /* Point DTA at password */ + r.x.ax = 0x1A00; + r.x.dx = (__tb & 0x0F); + r.x.ds = (__tb) >> 4; + __dpmi_int(0x21, &r); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.x.ax = 0x4303; /* Set rights */ + r.x.cx = rights; + r.x.dx = (__tb & 0x0F) + 0x10; + r.x.ds = (__tb) >> 4; + + __dpmi_int(0x21, &r); + + if (r.x.flags & 1) + { + redir_Msg(" Try 1 failed. Error %04x\n\r", r.x.ax); + if (redir_password_error()) + { + redir_password_append(path, dma); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.x.ax = 0x4303; /* Set rights */ + r.x.cx = rights; + r.x.dx = (__tb & 0x0F) + 0x10; + r.x.ds = (__tb) >> 4; + + __dpmi_int(0x21, &r); + if (!r.x.flags & 1) return 0; + if (redir_password_error()) return 0x7FF; + } + return 0xFF; + } + return 0; +} + +#else /* __GO32__ */ + +cpm_word redir_drdos_get_rights(char *path) +{ + union REGS r; + struct SREGS s; + + if (!redir_drdos) return 0; + + redir_Msg("Rights for file %s: \n\r", path); + + dosmemput(path, strlen(path) + 1, __tb); + r.w.ax = 0x4302; + r.w.dx = __tb & 0x0F; + s.ds = (__tb) >> 4; + + intdosx(&r, &r, &s); + + redir_Msg(" %04x \n\r", r.w.cx); + + if (r.w.cflag) return 0; + return r.w.cx; +} + + +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights) +{ + union REGS r; + struct SREGS s; + + if (!redir_drdos) return 0; + + redir_Msg("Put rights for file %s: %04x\n\r", path, rights); + + dosmemput(dma, 8, __tb); /* Point DTA at password */ + r.w.ax = 0x1A00; + r.w.dx = (__tb & 0x0F); + s.ds = (__tb) >> 4; + intdosx(&r, &r, &s); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.w.ax = 0x4303; /* Set rights */ + r.w.cx = rights; + r.w.dx = (__tb & 0x0F) + 0x10; + s.ds = (__tb) >> 4; + + intdosx(&r, &r, &s); + + if (r.w.cflag) + { + redir_Msg(" Try 1 failed. Error %04x \n\r", r.w.ax); + if (redir_password_error()) + { + redir_password_append(path, dma); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.w.ax = 0x4303; /* Set rights */ + r.w.cx = rights; + r.w.dx = (__tb & 0x0F) + 0x10; + s.ds = (__tb) >> 4; + + intdosx(&r, &r, &s); + if (!r.w.cflag) return 0; + } + return 0xFF; + } + return 0; +} + +#endif /* __GO32__ */ + + +cpm_word redir_password_error(void) +{ + union REGS r; + + if (!redir_drdos) return 0; + + r.w.ax = 0x5900; + r.w.bx = 0x0000; + + intdos(&r, &r); + + redir_Msg("Last error was: %04x\r\n", r.w.ax); + + if (r.w.ax == 0x56) return 1; /* Bad password */ + return 0; +} + + +void redir_password_append(char *s, cpm_byte *dma) +{ + int n, m; + + if (!redir_drdos) return; + + if (dma[0] == 0 || dma[0] == 0x20) return; + + strcat(s, ";"); + m = strlen(s); + + for (n = 0; n < 8; n++) + { + if (dma[n] == ' ') s[m] = 0; + else s[m] = dma[n]; + ++m; + } + s[m] = 0; + +} +#else /* __MSDOS__ */ +void redir_password_append(char *s, cpm_byte *dma) {} +cpm_word redir_password_error(void) { return 0; } +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights) +{ return 0; } +cpm_word redir_drdos_get_rights(char *path) { return 0; } +#endif /* __MSDOS__ */ + + diff --git a/Tools/unix/zx/edops.h b/Tools/unix/zx/edops.h new file mode 100644 index 00000000..6b04c96a --- /dev/null +++ b/Tools/unix/zx/edops.h @@ -0,0 +1,567 @@ +/* Emulations of the ED operations of the Z80 instruction set. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define input(var) { unsigned short u;\ + var=u=in(tstates,b,c);\ + tstates+=u>>8;\ + f=(f&1)|(var&0xa8)|((!var)<<6)|parity(var);\ + } +#define sbchl(x) { unsigned short z=(x);\ + unsigned long t=(hl-z-cy)&0x1ffff;\ + f=((t>>8)&0xa8)|(t>>16)|2|\ + (((hl&0xfff)<(z&0xfff)+cy)<<4)|\ + (((hl^z)&(hl^t)&0x8000)>>13)|\ + ((!(t&0xffff))<<6)|2;\ + l=t;\ + h=t>>8;\ + } + +#define adchl(x) { unsigned short z=(x);\ + unsigned long t=hl+z+cy;\ + f=((t>>8)&0xa8)|(t>>16)|\ + (((hl&0xfff)+(z&0xfff)+cy>0xfff)<<4)|\ + (((~hl^z)&(hl^t)&0x8000)>>13)|\ + ((!(t&0xffff))<<6)/*|2*/;\ + l=t;\ + h=t>>8;\ + } +/* [JCE] The "|2" should not be there, at least according to my tests on + * a PCW16. The PCW16's ADC always resets that bit. */ + + +#define neg (a=-a,\ + f=(a&0xa8)|((!a)<<6)|(((a&15)>0)<<4)|((a==128)<<2)|2|(a>0)) + +{ + unsigned char op=fetch(pc); + pc++; + radjust++; + switch(op){ +instr(0x40,8); + input(b); +endinstr; + +instr(0x41,8); + tstates+=out(tstates,b,c,b); +endinstr; + +instr(0x42,11); + sbchl(bc); +endinstr; + +instr(0x43,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2b(addr,b,c); + } +endinstr; + +instr(0x44,4); + neg; +endinstr; + +instr(0x45,4); + iff1=iff2; + ret; +endinstr; + +instr(0x46,4); + im=0; +endinstr; + +instr(0x47,5); + i=a; +endinstr; + +instr(0x48,8); + input(c); +endinstr; + +instr(0x49,8); + tstates+=out(tstates,b,c,c); +endinstr; + +instr(0x4a,11); + adchl(bc); +endinstr; + +instr(0x4b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + c=fetch(addr); + b=fetch(addr+1); + } +endinstr; + +instr(0x4c,4); + neg; +endinstr; + +instr(0x4d,4); + ret; +endinstr; + +instr(0x4e,4); + im=1; +endinstr; + +instr(0x4f,5); + r=a; + radjust=r; +endinstr; + +instr(0x50,8); + input(d); +endinstr; + +instr(0x51,8); + tstates+=out(tstates,b,c,d); +endinstr; + +instr(0x52,11); + sbchl(de); +endinstr; + +instr(0x53,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2b(addr,d,e); + } +endinstr; + +instr(0x54,4); + neg; +endinstr; + +instr(0x55,4); + ret; +endinstr; + +instr(0x56,4); + im=2; +endinstr; + +instr(0x57,5); + a=i; + f=(f&1)|(a&0xa8)|((!a)<<6)|(iff2<<2); +endinstr; + +instr(0x58,8); + input(e); +endinstr; + +instr(0x59,8); + tstates+=out(tstates,b,c,e); +endinstr; + +instr(0x5a,11); + adchl(de); +endinstr; + +instr(0x5b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + e=fetch(addr); + d=fetch(addr+1); + } +endinstr; + +instr(0x5c,4); + neg; +endinstr; + +instr(0x5d,4); + ret; +endinstr; + +instr(0x5e,4); + im=3; +endinstr; + +instr(0x5f,5); + r=(r&0x80)|(radjust&0x7f); + a=r; + f=(f&1)|(a&0xa8)|((!a)<<6)|(iff2<<2); +endinstr; + +instr(0x60,8); + input(h); +endinstr; + +instr(0x61,8); + tstates+=out(tstates,b,c,h); +endinstr; + +instr(0x62,11); + sbchl(hl); +endinstr; + +instr(0x63,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2b(addr,h,l); + } +endinstr; + +instr(0x64,4); + neg; +endinstr; + +instr(0x65,4); + ret; +endinstr; + +instr(0x66,4); + im=0; +endinstr; + +instr(0x67,14); + {unsigned char t=fetch(hl); + unsigned char u=(a<<4)|(t>>4); + a=(a&0xf0)|(t&0x0f); + store(hl,u); + f=(f&1)|(a&0xa8)|((!a)<<6)|parity(a); + } +endinstr; + +instr(0x68,8); + input(l); +endinstr; + +instr(0x69,8); + tstates+=out(tstates,b,c,l); +endinstr; + +instr(0x6a,11); + adchl(hl); +endinstr; + +instr(0x6b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + l=fetch(addr); + h=fetch(addr+1); + } +endinstr; + +instr(0x6c,4); + neg; +endinstr; + +instr(0x6d,4); + ret; +endinstr; + +instr(0x6e,4); + im=1; +endinstr; + +instr(0x6f,5); + {unsigned char t=fetch(hl); + unsigned char u=(a&0x0f)|(t<<4); + a=(a&0xf0)|(t>>4); + store(hl,u); + f=(f&1)|(a&0xa8)|((!a)<<6)|parity(a); + } +endinstr; + +instr(0x70,8); + {unsigned char x;input(x);} +endinstr; + +instr(0x71,8); + tstates+=out(tstates,b,c,0); +endinstr; + +instr(0x72,11); + sbchl(sp); +endinstr; + +instr(0x73,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2(addr,sp); + } +endinstr; + +instr(0x74,4); + neg; +endinstr; + +instr(0x75,4); + ret; +endinstr; + +instr(0x76,4); + im=2; +endinstr; + +instr(0x78,8); + input(a); +endinstr; + +instr(0x79,8); + tstates+=out(tstates,b,c,a); +endinstr; + +instr(0x7a,11); + adchl(sp); +endinstr; + +instr(0x7b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + sp=fetch2(addr); + } +endinstr; + +instr(0x7c,4); + neg; +endinstr; + +instr(0x7d,4); + ret; +endinstr; + +instr(0x7e,4); + im=3; +endinstr; + +instr(0xa0,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!++l)h++; + if(!++e)d++; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + } +endinstr; + +instr(0xa1,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!++l)h++; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + } +endinstr; + +instr(0xa2,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!++l)h++; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c)&4); + } +endinstr; + +instr(0xa3,12); /* I can't determine the correct flags outcome for the + block OUT instructions. Spec says that the carry + flag is left unchanged and N is set to 1, but that + doesn't seem to be the case... */ + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!++l)h++; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + } +endinstr; + +instr(0xa8,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!l--)h--; + if(!e--)d--; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + } +endinstr; + +instr(0xa9,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!l--)h--; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + } +endinstr; + +instr(0xaa,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!l--)h--; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c^4)&4); + } +endinstr; + +instr(0xab,12); + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!l--)h--; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + } +endinstr; + +/* Note: the Z80 implements "*R" as "*" followed by JR -2. No reason + to change this... */ + +instr(0xb0,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!++l)h++; + if(!++e)d++; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + if(b|c)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb1,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!++l)h++; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + if((f&0x44)==4)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb2,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!++l)h++; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c)&4); + if(b)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb3,12); + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!++l)h++; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + if(b)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb8,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!l--)h--; + if(!e--)d--; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + if(b|c)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb9,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!l--)h--; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + if((f&0x44)==4)pc-=2,tstates+=5; + } +endinstr; + +instr(0xba,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!l--)h--; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c^4)&4); + if(b)pc-=2,tstates+=5; + } +endinstr; + +instr(0xbb,12); + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!l--)h--; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + if(b)pc-=2,tstates+=5; + } +endinstr; + +/* XZ80 Pseudo-ops +instr(0xfb,4); + multiloader(hl,a); +endinstr; + +instr(0xfc,4); + { int ans; + stopwatch(); + ans=loader(pc,ix,de,a,cy); + if(ans>=0){ + if(ans==1)pc=0x0806; + else if(ans==2)f&=254; + else f|=1; + ix+=de; + d=e=0; + if(ans!=1)ret; + } + startwatch(1); + } +endinstr; + +instr(0xfd,4); + { int ans; + stopwatch(); + ans=saver(pc,ix,de,a); + if(ans>0)pc=0x0806; + else if(ans==0){ + ix+=de; + d=e=0; + ret; + } + startwatch(1); + } +endinstr; +*/ + +/* ZXCC pseudo-op */ +instr(0xfe, 4); +{ + /* Create copies of the registers here so we can take their addresses + * and not lose register optimisation in the rest of the CPU code */ + byte xa,xb,xc,xd,xe,xf,xxh,xxl; + word xp,xx,xy; + + xa = a; xb = b; xc = c; xd = d; xe = e; xf = f; xxh = h; xxl = l; + xp = pc; xx = ix; xy = iy; + + ed_fe(&xa,&xb,&xc,&xd,&xe,&xf,&xxh,&xxl,&xp,&xx,&xy); + + a = xa; b = xb; c = xc; d = xd; e = xe; f = xf; h = xxh; l = xxl; + pc = xp; ix = xx; iy = xy; +} +endinstr; + +default: tstates+=4; + +}} diff --git a/Tools/unix/zx/readme.txt b/Tools/unix/zx/readme.txt new file mode 100644 index 00000000..31c3e320 --- /dev/null +++ b/Tools/unix/zx/readme.txt @@ -0,0 +1,47 @@ +ZX Command + +An adaptation of zxcc-0.5.6 by Wayne Warthen + +This directory contains the source files used to build the "zx" tool. This tool +is essentially just John Elliott's zxcc package version zxcc-0.5.6 modified to +build for Windows and simplified down to just a single command (zx) +which is essentially just the zxcc command. + +Please see http://www.seasip.info/Unix/Zxcc/ for more information on zxcc. + +To build under Open Watcom or Microsoft Visual C++, use the following command: + + cl /Fe"zx.exe" zx.c cpmdrv.c cpmglob.c cpmparse.c cpmredir.c drdos.c util.c xlt.c zxbdos.c zxcbdos.c zxdbdos.c z80.c dirent.c + +To build a debug version, use the following command: + + cl /DDEBUG /Fe"zxdbg.exe" zx.c cpmdrv.c cpmglob.c cpmparse.c cpmredir.c drdos.c util.c xlt.c zxbdos.c zxcbdos.c zxdbdos.c z80.c dirent.c + +WARNING: There seems to be a rare scenario that breaks zx under the Open Watcom build. CP/M allows a file to be accessed +under multiple FCB's without an error. Open Watcom will see this as an error. At present, the only tool I know of that does +this is M80. + +December 5, 2014 + +After struggling to get the entire zxcc package to build nicely using autoconf, +I finally gave up and took a much more direct approach. I have extracted just +the source files needed and created a simple batch file to build the tool. I +realize this could be done much better, but I cheated in the interest of time. + +The one "real" change I made in the source code was that I modified the tool +to look for bios.bin in the same directory as the executable is in. This +just makes it much easier to set up (for me, anyway). + +The GPL status of everything remains in place and carries forward. + +Wayne Warthen +wwarthen@gmail.com + +March 15, 2017 + +- Updated to compile under Open Watcom. +- Implemented BDOS console status function. +- Set stdin and stdout to binary mode at startup. + +Wayne Warthen +wwarthen@gmail.com \ No newline at end of file diff --git a/Tools/unix/zx/util.c b/Tools/unix/zx/util.c new file mode 100644 index 00000000..21d29728 --- /dev/null +++ b/Tools/unix/zx/util.c @@ -0,0 +1,378 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds miscellaneous utility functions. +*/ + +#include "cpmint.h" +#include + +/* In debug mode, lseek()s can be traced. */ + +#ifdef DEBUG + +long zxlseek(int fd, long offset, int wh) +{ +#ifdef WIN32 + long v; + redir_Msg(">SetFilePointer() Handle=%lu, Offset=%lu, Method=%lu\n", fd, offset, wh); + v = SetFilePointer((HANDLE)fd, offset, NULL, wh); + redir_Msg("= 0) return v; + + redir_Msg("lseek fails with errno = %d\n", errno); + if (errno == EBADF) redir_Msg(" (bad file descriptor %d)\n", fd); + if (errno == ESPIPE) redir_Msg(" (file %d is a pipe)\n", fd); + if (errno == EINVAL) redir_Msg(" (bad parameter %d)\n", wh); + + return -1; +#endif +} + +void redir_showfcb(cpm_byte *fd) +{ + int n; + + for (n = 0; n < 32; n++) + { + if (!n || n>= 12) printf("%02x ", fd[n]); + else printf("%c", fd[n] & 0x7F); + } + printf("\r\n"); +} + +#else + +long zxlseek(int fd, long offset, int wh) +{ +#ifdef WIN32 + return SetFilePointer((HANDLE)fd, offset, NULL, wh); +#else + return lseek(fd, offset, wh); +#endif +} + + +#endif + +/* Get the "sequential access" file pointer out of an FCB */ + +long redir_get_fcb_pos(cpm_byte *fcb) +{ + long npos; + + npos = 524288L * fcb[0x0E]; /* S2 */ + npos += 16384L * fcb[0x0C]; /* Extent */ + npos += 128L * fcb[0x20]; /* Record */ + + return npos; +} + +void redir_put_fcb_pos(cpm_byte *fcb, long npos) +{ + fcb[0x20] = (npos / 128) % 128; + fcb[0x0C] = (npos / 16384) % 32; + fcb[0x0E] = (npos / 524288L) % 64; +} + + +/* + * find a filename that works. + * note that this is where we handle the case sensitivity/non-case sensitivity + * horror. + * the name that is passed in should be in lower case. + * we'll modify it to the first one that matches + */ +void +swizzle(char *fullpath) +{ + struct stat ss; + char *slash; + DIR *dirp; + struct dirent *dentry; + + /* short circuit if ok */ + if (stat(fullpath, &ss) == 0) { + return; + } + + slash = rindex(fullpath, '/'); + if (!slash) { + return; + } + *slash = '\0'; + dirp = opendir(fullpath); + *slash = '/'; + while ((dentry = readdir(dirp)) != NULL) { + if (strcasecmp(dentry->d_name, slash + 1) == 0) { + strcpy(slash + 1, dentry->d_name); + break; + } + } + closedir(dirp); +} + +/* + * Passed a CP/M FCB, convert it to a unix filename. Turn its drive back into + * a path. + */ + +int redir_fcb2unix(cpm_byte *fcb, char *fname) +{ + int n, q, drv, ddrv; + char s[2]; + + s[1] = 0; + q = 0; + drv = fcb[0] & 0x7F; + if (drv == '?') drv = 0; + + ddrv = fcb[0] & 0x7F; + if (ddrv < 0x1F) ddrv += '@'; + + redir_Msg("%c:%-8.8s.%-3.3s\n", + ddrv, + fcb + 1, + fcb + 9); + + if (!drv) strcpy(fname, redir_drive_prefix[redir_cpmdrive]); + else strcpy(fname, redir_drive_prefix[drv - 1]); + + for (n = 1; n < 12; n++) + { + s[0] = (fcb[n] & 0x7F); + if (s[0] == '?') q = 1; + if (isupper(s[0])) s[0] = tolower(s[0]); + if (s[0] != ' ') + { + if (n == 9) strcat(fname, "."); + strcat(fname, s); + } + } + return q; +} + +#ifndef EROFS /* Open fails because of read-only FS */ +#define EROFS EACCES +#endif + +int redir_ofile(cpm_byte *fcb, char *s) +{ + int h, rv; + + /* Software write-protection */ +#ifdef WIN32 + redir_Msg(">CreateFile([OPEN_EXISTING]) Name='%s'\n", s); + h = (int)CreateFile(s, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + redir_Msg("= 0 || (errno != EACCES && errno != EROFS)) return h; + } + redir_Msg("**2**"); + h = open(s, O_RDONLY | O_BINARY); + if (h < 0) return -1; + fcb[9] |= 0x80; + #endif +#endif + + return h; +} + + +/* Extract a file handle from where it was stored in an FCB by fcb_open() + or fcb_creat(). Aborts if the FCB has been tampered with. + + Note: Some programs (like GENCOM) close FCBs they never opened. This causes + the Corrupt FCB message, but no harm seems to ensue. */ + +int redir_verify_fcb(cpm_byte *fcb) +{ + if (fcb[16] != 0xFD || fcb[17] != 0x00) + { + fprintf(stderr,"cpmredir: Corrupt FCB\n"); + return -1; + } + return (int)(redir_rd32(fcb + 18)); + +} + +/* Print a trace message */ + +#ifdef DEBUG + +void redir_Msg(char *s, ...) +{ + va_list ap; + + va_start(ap, s); + printf("cpmredir trace: "); + vprintf(s, ap); + va_end(ap); + fflush(stdout); +} + +#endif + +#define BCD(x) (((x % 10)+16*(x/10)) & 0xFF) + +/* Convert time_t to CP/M day count/hours/minutes */ +dword redir_cpmtime(time_t t) +{ + long d = (t / 86400) - 2921; /* CP/M day 0 is unix day 2921 */ + long h = (t % 86400) / 3600; /* Hour, 0-23 */ + long m = (t % 3600) / 60; /* Minute, 0-59 */ + + return (d | (BCD(h) << 16) | (BCD(m) << 24)); +} + +#undef BCD + +#define UNBCD(x) (((x % 16) + 10 * (x / 16)) & 0xFF) + +time_t redir_unixtime(cpm_byte *c) +{ + time_t t; + cpm_word days; + + days = (c[0] + 256 * c[1]) + 2921; + + t = 60L * UNBCD(c[3]); + t += 3600L * UNBCD(c[2]); + t += 86400L * days; + + return t; +} + +#undef UNBCD + + +/* Functions to access 24-bit & 32-bit words in memory. These are always + little-endian. */ + +void redir_wr24(cpm_byte *addr, dword v) +{ + addr[0] = v & 0xFF; + addr[1] = (v >> 8) & 0xFF; + addr[2] = (v >> 16) & 0xFF; +} + +void redir_wr32(cpm_byte *addr, dword v) +{ + addr[0] = v & 0xFF; + addr[1] = (v >> 8) & 0xFF; + addr[2] = (v >> 16) & 0xFF; + addr[3] = (v >> 24) & 0xFF; +} + +dword redir_rd24(cpm_byte *addr) +{ + register dword rv = addr[2]; + + rv = (rv << 8) | addr[1]; + rv = (rv << 8) | addr[0]; + return rv; +} + + +dword redir_rd32(cpm_byte *addr) +{ + register dword rv = addr[3]; + + rv = (rv << 8) | addr[2]; + rv = (rv << 8) | addr[1]; + rv = (rv << 8) | addr[0]; + return rv; +} + + +void redir_log_drv(cpm_byte drv) +{ + if (!drv) redir_l_drives |= 1; + else redir_l_drives |= (1L << drv); +} + +void redir_log_fcb(cpm_byte *fcb) +{ + int drv = fcb[0] & 0x7F; + + if (drv && drv != '?') redir_log_drv(drv - 1); + else redir_log_drv(redir_cpmdrive); +} + + +int redir_ro_drv(cpm_byte drv) +{ + if (!drv) return redir_ro_drives & 1; + else return redir_ro_drives & (1L << drv); +} + +int redir_ro_fcb(cpm_byte *fcb) +{ + int drv = fcb[0] & 0x7F; + + if (drv && drv != '?') return redir_ro_drv(drv - 1); + else return redir_ro_drv(redir_cpmdrive); +} + + + +cpm_word redir_xlt_err(void) +{ + if (redir_password_error()) return 0x7FF; /* DRDOS pwd error */ + switch(errno) + { + case EISDIR: + case EBADF: return 9; /* Bad FCB */ + case EINVAL: return 0x03FF; /* Readonly file */ + case EPIPE: return 0x01FF; /* Broken pipe */ + case ENOSPC: return 1; /* No space */ + default: return 0xFF; /* Software error */ + } +} + diff --git a/Tools/unix/zx/xlt.c b/Tools/unix/zx/xlt.c new file mode 100644 index 00000000..f0ba1da5 --- /dev/null +++ b/Tools/unix/zx/xlt.c @@ -0,0 +1,191 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds functions dealing with name translation; also the + initialisation code. +*/ + +#include "cpmint.h" + +/* Detect DRDOS */ + +#ifdef __MSDOS__ +static void drdos_init(void) +{ + +/* The DJGPP DOS extender won't detect DRDOS using intdos(), so we have + to use __dpmi_int() instead. */ + +#ifdef __GO32__ + __dpmi_regs ir; + + ir.x.ax = 0x4452; /* "DR" */ + + __dpmi_int(0x21, &ir); + if (ir.x.flags & 1) return; /* Not DRDOS */ + + redir_Msg("DRDOS detected.\r\n"); + + redir_drdos = 1; + +#else /* __GO32__ */ + + union REGS ir, or; + + ir.w.ax = 0x4452; /* "DR" */ + + intdos(&ir, &or); + if (or.w.cflag) return; /* Not DRDOS */ + + redir_Msg("DRDOS detected.\r\n"); + + redir_drdos = 1; +#endif /* __GO32__ */ +} +#endif /* __MSDOS__ */ + + + +int fcb_init(void) +{ + int n; + + /* A: to O: free */ + for (n = 0; n < 15; n++) redir_drive_prefix[n][0] = 0; + + strcpy(redir_drive_prefix[15], "./"); /* P: is current directory */ + + /* Log on to P:. It is the only drive at this point which we + * know works. */ + redir_cpmdrive = 15; +#ifdef __MSDOS__ + drdos_init(); +#endif + + return 1; +} + +/* Deinitialise the library. */ + +void fcb_deinit(void) +{ + /* Nothing */ +} + +/* Translate a name from the host FS to a CP/M name. This will (if necessary) + * create a mapping between a CP/M drive and a host directory path. + * + * CP/M drives A: to O: can be mapped in this way. P: is always the current + * drive. + * + */ + +void xlt_name(char *localname, char *cpmname) +{ + char ibuf[CPM_MAXPATH + 1]; + char nbuf[CPM_MAXPATH + 1]; + char *pname; + int n; + + sprintf(ibuf, "%-.*s", CPM_MAXPATH, localname); + pname = strrchr(ibuf, '/'); +#ifdef __MSDOS__ + if (!pname) pname = strrchr(ibuf,'\\'); + if (!pname) pname = strrchr(ibuf,':'); +#endif + if (!pname) /* No path separators in the name. It is therefore a + local filename, so map it to drive P: */ + { + strcpy(cpmname, "p:"); + strcat(cpmname, ibuf); + return; + } + ++pname; + strcpy(nbuf, pname); /* nbuf holds filename component */ + *pname = 0; /* ibuf holds path component */ + + /* See if the path is one of those already mapped to drives */ + + for (n = 0; n < 15; n++) + { + if (redir_drive_prefix[n][0] && !strcmp(ibuf, redir_drive_prefix[n])) + { + sprintf(cpmname,"%c:%s", n + 'a', nbuf); + return; + } + } + + /* It is not, see if another drive can be allocated */ + + for (n = 0; n < 15; n++) if (!redir_drive_prefix[n][0]) + { + strcpy(redir_drive_prefix[n], ibuf); + sprintf(cpmname,"%c:%s", n + 'a', nbuf); + return; + } + + /* No other drive can be allocated */ + + strcpy(cpmname,"p:"); + strcat(cpmname, nbuf); +} + +/* It is sometimes convenient to set some fixed mappings. This will create + * a mapping for a given directory. + * Pass drive = -1 for "first available", or 0-15 for A: to P: + */ + +int xlt_map(int drive, char *localdir) +{ + int n; + + if (drive == -1) + { + for (n = 0; n < 15; n++) if (!redir_drive_prefix[n][0]) + { + drive = n; + break; + } + if (drive == -1) return 0; /* No space for mappings */ + } + if (redir_drive_prefix[drive][0]) return 0; /* Drive taken */ + + sprintf(redir_drive_prefix[drive], "%-.*s", CPM_MAXPATH, localdir); + return 1; +} + + +/* Unmap a drive + */ + +int xlt_umap(int drive) +{ + if (!redir_drive_prefix[drive][0]) return 0; /* Drive not taken */ + redir_drive_prefix[drive][0] = 0; + return 1; +} + + +char *xlt_getcwd(int drive) +{ + if (drive < 0 || drive > 16) return ""; + + return redir_drive_prefix[drive]; +} + diff --git a/Tools/unix/zx/z80.c b/Tools/unix/zx/z80.c new file mode 100644 index 00000000..d1d99cd5 --- /dev/null +++ b/Tools/unix/zx/z80.c @@ -0,0 +1,270 @@ +/* Emulation of the Z80 CPU with hooks into the other parts of xz80. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "zx.h" + +#define parity(a) (partable[a]) + +unsigned char partable[256]={ + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4 + }; + +#ifdef DEBUG +static unsigned short breakpoint=0; +static unsigned int breaks=0; + +//static void inline log(fp,name,val) +static void log(fp,name,val) +FILE *fp; +char *name; +unsigned short val; +{ + int i; + fprintf(fp,"%s=%04X ",name,val); + for(i=0;i<8;i++,val++)fprintf(fp," %02X",fetch(val)); + putc('\n',fp); +} +#endif + +void mainloop(word spc, word ssp){ + register unsigned char a, f, b, c, d, e, h, l; + unsigned char r, a1, f1, b1, c1, d1, e1, h1, l1, i, iff1, iff2, im; + register unsigned short pc; + unsigned short ix, iy, sp; + register unsigned long tstates; + register unsigned int radjust; + register unsigned char ixoriy, new_ixoriy; + unsigned char intsample; + register unsigned char op; +#ifdef DEBUG + char flags[9]; + int bit; + FILE *fp=0; + register unsigned short af2=0,bc2=0,de2=0,hl2=0,ix2=0,iy2=0,sp2=0; + register unsigned char i2=0; + //unsigned char *memory=memptr[0]; + struct _next {unsigned char bytes[8];} *next; + unsigned short BC, DE, HL, AF; + + fputs("Press F11 to log\n",stderr); +#endif + a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1=h1=l1=i=r=iff1=iff2=im=0; + ixoriy=new_ixoriy=0; + ix=iy=0; + pc=spc; + sp=ssp; + tstates=radjust=0; + while(1){ + ixoriy=new_ixoriy; + new_ixoriy=0; +#ifdef DEBUG + next=(struct _next *)&fetch(pc); + BC=bc;DE=de;HL=hl;AF=(a<<8)|f; + if(fp && !ixoriy){ + log(fp,"pc",pc); + if(sp!=sp2)log(fp,"sp",sp2=sp); + if(iy!=iy2)log(fp,"iy",iy2=iy); + if(ix!=ix2)log(fp,"ix",ix2=ix); + if(hl!=hl2)log(fp,"hl",hl2=hl); + if(de!=de2)log(fp,"de",de2=de); + if(bc!=bc2)log(fp,"bc",bc2=bc); + if(((a<<8)|f)!=af2){ + af2=(a<<8)|f; + strcpy(flags,"SZ H VNC"); + for(bit=0;bit<8;bit++)if(!(f&(1<<(7-bit))))flags[bit]=' '; + fprintf(fp,"af=%04X %s\n",af2,flags); + } + if(i!=i2)fprintf(fp,"ir=%02X%02X\n",i2=i,r); + putc('\n',fp); + } + if(pc==breakpoint && pc) + breaks++; /* some code at which to set a breakpoint */ + a=AF>>8; f=AF; h=HL>>8; l=HL; d=DE>>8; e=DE; b=BC>>8; c=BC; +#endif +/* +{ + static int tr = 0; + static int id = 0; +// static byte b = 0; +// + if (pc == 0x1177) tr = 1; + if (pc == 0x1185) tr = 0; + if (tr >= 1) ++id; + if (tr >= 1) printf("%d: PC=%04x %02x AF=%02x:%02x BC=%04x DE=%04x HL=%04x IX=%04x IY=%04x\r\n", + id, pc, fetch(pc), a,f, bc, de, hl, ix, iy); +} +*/ + intsample=1; + op=fetch(pc); + pc++; + radjust++; + switch(op){ +#include "z80ops.h" + } +/*** + * ZXCC doesn't do interrupts at all, so all this is commented out + if(tstates>=int_cycles && intsample){ + tstates-=int_cycles; + frames++; + // Carry out X-related tasks (including waiting for timer + // signal if necessary) + switch(interrupt()){ + case Z80_quit: +#ifdef DEBUG + if(fp)fclose(fp); +#endif + return; + case Z80_NMI: + if(fetch(pc)==0x76)pc++; + iff2=iff1; + iff1=0; + // The Z80 performs a machine fetch cycle for 5 Tstates + // but ignores the result. It takes a further 10 Tstates + // to jump to the NMI routine at 0x66. + tstates+=15; + push2(pc); + pc=0x66; + break; + case Z80_reset: + a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1= + h1=l1=i=r=iff1=iff2=im=0; + ix=iy=sp=pc=0; + radjust=0; + break; +#ifdef DEBUG + case Z80_log: + if(fp){ + fclose(fp); + fp=0; + fputs("Logging turned off\n",stderr); + } else { + fp=fopen(config.log,"a"); + if(fp)fprintf(stderr,"Logging to file %s\n",config.log); + else perror(config.log); + } + break; +#endif + + case Z80_load: + stopwatch(); + if(snapload()){ + a=snapa; + f=snapf; + b=snapb; + c=snapc; + d=snapd; + e=snape; + h=snaph; + l=snapl; + a1=snapa1; + f1=snapf1; + b1=snapb1; + c1=snapc1; + d1=snapd1; + e1=snape1; + h1=snaph1; + l1=snapl1; + iff1=snapiff1; + iff2=snapiff2; + i=snapi; + r=snapr; + radjust=r; + im=snapim; + ix=snapix; + iy=snapiy; + sp=snapsp; + pc=snappc; + } + startwatch(1); + break; + case Z80_save: + r=(r&0x80)|(radjust&0x7f); + snapa=a; + snapf=f; + snapb=b; + snapc=c; + snapd=d; + snape=e; + snaph=h; + snapl=l; + snapa1=a1; + snapf1=f1; + snapb1=b1; + snapc1=c1; + snapd1=d1; + snape1=e1; + snaph1=h1; + snapl1=l1; + snapiff1=iff1; + snapiff2=iff2; + snapi=i; + snapr=r; + snapim=im; + snapix=ix; + snapiy=iy; + snapsp=sp; + snappc=pc; + snapsave(); + startwatch(1); + break; + + } + if(iff1){ +#ifdef DEBUG + if(fp)fprintf(fp,"Interrupt (im=%d)\n\n",im); +#endif + if(fetch(pc)==0x76)pc++; + iff1=iff2=0; + tstates+=5; // accompanied by an input from the data bus // + switch(im){ + case 0: // IM 0 // + case 1: // undocumented // + case 2: // IM 1 // + // there is little to distinguish between these cases // + tstates+=8; + push2(pc); + pc=0x38; + break; + case 3: // IM 2 // + tstates+=14; + { + int addr=fetch2((i<<8)|0xff); + push2(pc); + pc=addr; + } + } + } + }*/ + } +} diff --git a/Tools/unix/zx/z80.h b/Tools/unix/zx/z80.h new file mode 100644 index 00000000..89e12a84 --- /dev/null +++ b/Tools/unix/zx/z80.h @@ -0,0 +1,86 @@ +/* Miscellaneous definitions for xz80, copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* [John Elliott, 15 July 2001] + * Copied this file into ZXCC, a CP/M emulator. + * Since ZXCC's memory is a flat 64k space and will never be bank-switched, + * the bank-switching code is removed. + * Since ZXCC has no memory-mapped screen, all the screen management code + * goes as well. + * Since ZXCC doesn't need its speed regulated, all the speed regulation + * code goes as well. + * Since ZXCC doesn't save or load snapshots... OK, you get the idea. + */ + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#define Z80_quit 1 +#define Z80_NMI 2 +#define Z80_reset 3 +#define Z80_load 4 +#define Z80_save 5 +#define Z80_log 6 + +unsigned int in(); +unsigned int out(); +//int interrupt(); +int snapload(); +void snapsave(); +void mainloop(word xpc, word xsp); +void eachframe(); +void itimeron(); +void itimeroff(); +void startwatch(); +unsigned long stopwatch(); +void requester(); +int loader(); +int saver(); +void multiloader(); +void usage(); +void version(); +void drawborder(); + +#define fetch(x) (RAM[x]) +#define fetch2(x) ((fetch((x)+1)<<8)|fetch(x)) + +#define store(x,y) do { RAM[(x)] = (y); } while(0) + +#define store2b(x,hi,lo) do {\ + RAM[(x)]=(lo); \ + RAM[((x+1) & 0xFFFF)]=(hi); } while(0) + +#define store2(x,y) store2b(x,(y)>>8,y) + +#ifdef __GNUC__ +static void inline storefunc(unsigned short ad,unsigned char b){ + store(ad,b); +} +#undef store +#define store(x,y) storefunc(x,y) + +static void inline store2func(unsigned short ad,unsigned char b1,unsigned char b2){ + store2b(ad,b1,b2); +} +#undef store2b +#define store2b(x,hi,lo) store2func(x,hi,lo) +#endif + +#define bc ((b<<8)|c) +#define de ((d<<8)|e) +#define hl ((h<<8)|l) diff --git a/Tools/unix/zx/z80ops.h b/Tools/unix/zx/z80ops.h new file mode 100644 index 00000000..a9687741 --- /dev/null +++ b/Tools/unix/zx/z80ops.h @@ -0,0 +1,1304 @@ +/* Emulations of the Z80 CPU instruction set - part of xz80. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define instr(opcode,cycles) case opcode: {tstates+=cycles +#define HLinstr(opcode,cycles,morecycles) \ + case opcode: {unsigned short addr; \ + tstates+=cycles; \ + if(ixoriy==0)addr=hl; \ + else tstates+=morecycles, \ + addr=(ixoriy==1?ix:iy)+ \ + (signed char)fetch(pc),\ + pc++ +#define endinstr }; break + +#define cy (f&1) + +#define xh (ixoriy==0?h:ixoriy==1?(ix>>8):(iy>>8)) +#define xl (ixoriy==0?l:ixoriy==1?(ix&0xff):(iy&0xff)) + +#define setxh(x) (ixoriy==0?(h=(x)):ixoriy==1?(ix=(ix&0xff)|((x)<<8)):\ + (iy=(iy&0xff)|((x)<<8))) +#define setxl(x) (ixoriy==0?(l=(x)):ixoriy==1?(ix=(ix&0xff00)|(x)):\ + (iy=(iy&0xff00)|(x))) + +#define inc(var) /* 8-bit increment */ ( var++,\ + f=(f&1)|(var&0xa8)|\ + ((!(var&15))<<4)|((!var)<<6)|\ + ((var==128)<<2)\ + ) +#define dec(var) /* 8-bit decrement */ ( f=(f&1)|((!(var&15))<<4)|2,\ + --var,\ + f|=(var&0xa8)|((var==127)<<2)|\ + ((!var)<<6)\ + ) +#define swap(x,y) {unsigned char t=x; x=y; y=t;} +#define addhl(hi,lo) /* 16-bit add */ if(!ixoriy){\ + unsigned short t;\ + l=t=l+(lo);\ + f=(f&0xc4)|(((t>>=8)+(h&0x0f)+((hi)&0x0f)>15)<<4);\ + h=t+=h+(hi);\ + f|=(h&0x28)|(t>>8);\ + }\ + else do{unsigned long t=(ixoriy==1?ix:iy);\ + f=(f&0xc4)|(((t&0xfff)+((hi<<8)|lo)>0xfff)<<4);\ + t+=(hi<<8)|lo;\ + if(ixoriy==1)ix=t; else iy=t;\ + f|=((t>>8)&0x28)|(t>>16);\ + } while(0) +#define adda(x,c) /* 8-bit add */ do{unsigned short y;\ + unsigned char z=(x);\ + y=a+z+(c);\ + f=(y&0xa8)|(y>>8)|(((a&0x0f)+(z&0x0f)+(c)>15)<<4)|\ + (((~a^z)&0x80&(y^a))>>5);\ + f|=(!(a=y))<<6;\ + } while(0) +#define suba(x,c) /* 8-bit subtract */ do{unsigned short y;\ + unsigned char z=(x);\ + y=(a-z-(c))&0x1ff;\ + f=(y&0xa8)|(y>>8)|(((a&0x0f)<(z&0x0f)+(c))<<4)|\ + (((a^z)&0x80&(y^a))>>5)|2;\ + f|=(!(a=y))<<6;\ + } while(0) +#define cpa(x) /* 8-bit compare */ do{unsigned short y;\ + unsigned char z=(x);\ + y=(a-z)&0x1ff;\ + f=(y&0xa8)|(y>>8)|(((a&0x0f)<(z&0x0f))<<4)|\ + (((a^z)&0x80&(y^a))>>5)|2|((!y)<<6);\ + } while(0) +#define anda(x) /* logical and */ do{\ + a&=(x);\ + f=(a&0xa8)|((!a)<<6)|0x10|parity(a);\ + } while(0) +#define xora(x) /* logical xor */ do{\ + a^=(x);\ + f=(a&0xa8)|((!a)<<6)|parity(a);\ + } while(0) +#define ora(x) /* logical or */ do{\ + a|=(x);\ + f=(a&0xa8)|((!a)<<6)|parity(a);\ + } while(0) + +#define jr /* execute relative jump */ do{int j=(signed char)fetch(pc);\ + pc+=j+1;\ + tstates+=5;\ + } while(0) +#define jp /* execute jump */ (pc=fetch2(pc)) +#define call /* execute call */ do{\ + tstates+=7;\ + push2(pc+2);\ + jp;\ + } while(0) +#define ret /* execute return */ do{\ + tstates+=6;\ + pop2(pc);\ + } while(0) +#define pop2(var) /* pop 16-bit register */ (var=fetch2(sp),sp+=2) +#define pop1(v1,v2) /* pop register pair */ (v2=fetch(sp),\ + v1=fetch(sp+1),sp+=2) +#define push2(val) /* push 16-bit register */ do{sp-=2;store2(sp,(val));}\ + while(0) +#define push1(v1,v2) /* push register pair */ do{sp-=2;\ + store2b(sp,v1,v2);\ + }while(0) + +instr(0,4); + /* nop */ +endinstr; + +instr(1,10); + c=fetch(pc),pc++; + b=fetch(pc),pc++; +endinstr; + +instr(2,7); + store(bc,a); +endinstr; + +instr(3,6); + if(!++c)b++; +endinstr; + +instr(4,4); + inc(b); +endinstr; + +instr(5,4); + dec(b); +endinstr; + +instr(6,7); + b=fetch(pc),pc++; +endinstr; + +instr(7,4); + a=(a<<1)|(a>>7); + f=(f&0xc4)|(a&0x29); +endinstr; + +instr(8,4); + swap(a,a1); + swap(f,f1); +endinstr; + +instr(9,11); + addhl(b,c); +endinstr; + +instr(10,7); + a=fetch(bc); +endinstr; + +instr(11,6); + if(!c--)b--; +endinstr; + +instr(12,4); + inc(c); +endinstr; + +instr(13,4); + dec(c); +endinstr; + +instr(14,7); + c=fetch(pc),pc++; +endinstr; + +instr(15,4); + f=(f&0xc4)|(a&1); + a=(a>>1)|(a<<7); + f|=a&0x28; +endinstr; + +instr(16,8); + if(!--b)pc++; + else jr; +endinstr; + +instr(17,10); + e=fetch(pc),pc++; + d=fetch(pc),pc++; +endinstr; + +instr(18,7); + store(de,a); +endinstr; + +instr(19,6); + if(!++e)d++; +endinstr; + +instr(20,4); + inc(d); +endinstr; + +instr(21,4); + dec(d); +endinstr; + +instr(22,7); + d=fetch(pc),pc++; +endinstr; + +instr(23,4); + {int t=a>>7; + a=(a<<1)|(f&1); + f=(f&0xc4)|(a&0x28)|t; + } +endinstr; + +instr(24,7); + jr; +endinstr; + +instr(25,11); + addhl(d,e); +endinstr; + +instr(26,7); + a=fetch(de); +endinstr; + +instr(27,6); + if(!e--)d--; +endinstr; + +instr(28,4); + inc(e); +endinstr; + +instr(29,4); + dec(e); +endinstr; + +instr(30,7); + e=fetch(pc),pc++; +endinstr; + +instr(31,4); + {int t=a&1; + a=(a>>1)|(f<<7); + f=(f&0xc4)|(a&0x28)|t; + } +endinstr; + +instr(32,7); + if(f&0x40)pc++; + else jr; +endinstr; + +instr(33,10); + if(!ixoriy){ + l=fetch(pc),pc++; + h=fetch(pc),pc++; + } + else { + if(ixoriy==1)ix=fetch2(pc); + else iy=fetch2(pc); + pc+=2; + } +endinstr; + +instr(34,16); + {unsigned short addr=fetch2(pc); + pc+=2; + if(!ixoriy)store2b(addr,h,l); + else if(ixoriy==1)store2(addr,ix); + else store2(addr,iy); + } +endinstr; + +instr(35,6); + if(!ixoriy){if(!++l)h++;} + else if(ixoriy==1)ix++; + else iy++; +endinstr; + +instr(36,4); + if(ixoriy==0)inc(h); + else{unsigned char t; + t=(ixoriy==1?ix:iy)>>8; + inc(t); + if(ixoriy==1)ix=(ix&0xff)|(t<<8); + else iy=(iy&0xff)|(t<<8); + } +endinstr; + +instr(37,4); + if(ixoriy==0)dec(h); + else{unsigned char t; + t=(ixoriy==1?ix:iy)>>8; + dec(t); + if(ixoriy==1)ix=(ix&0xff)|(t<<8); + else iy=(iy&0xff)|(t<<8); + } +endinstr; + +instr(38,7); + setxh(fetch(pc)); + pc++; +endinstr; + +instr(39,4); + { + unsigned char incr=0, carry=cy; + if((f&0x10) || (a&0x0f)>9) incr=6; + if((f&1) || (a>>4)>9) incr|=0x60; + if(f&2)suba(incr,0); + else { + if(a>0x90 && (a&15)>9)incr|=0x60; + adda(incr,0); + } + f=((f|carry)&0xfb)|parity(a); + } +endinstr; + +instr(40,7); + if(f&0x40)jr; + else pc++; +endinstr; + +instr(41,11); + if(!ixoriy)addhl(h,l); + else if(ixoriy==1)addhl((ix>>8),(ix&0xff)); + else addhl((iy>>8),(iy&0xff)); +endinstr; + +instr(42,16); + {unsigned short addr=fetch2(pc); + pc+=2; + if(!ixoriy){ + l=fetch(addr); + h=fetch(addr+1); + } + else if(ixoriy==1)ix=fetch2(addr); + else iy=fetch2(addr); + } +endinstr; + +instr(43,6); + if(!ixoriy){if(!l--)h--;} + else if(ixoriy==1)ix--; + else iy--; +endinstr; + +instr(44,4); + if(!ixoriy)inc(l); + else {unsigned char t; + t=(ixoriy==1?ix:iy); + inc(t); + if(ixoriy==1)ix=(ix&0xff00)|t; + else iy=(iy&0xff00)|t; + } +endinstr; + +instr(45,4); + if(!ixoriy)dec(l); + else {unsigned char t; + t=(ixoriy==1?ix:iy); + dec(t); + if(ixoriy==1)ix=(ix&0xff00)|t; + else iy=(iy&0xff00)|t; + } +endinstr; + +instr(46,7); + setxl(fetch(pc)); + pc++; +endinstr; + +instr(47,4); + a=~a; + f=(f&0xc5)|(a&0x28)|0x12; +endinstr; + +instr(48,7); + if(f&1)pc++; + else jr; +endinstr; + +instr(49,10); + sp=fetch2(pc); + pc+=2; +endinstr; + +instr(50,13); + {unsigned short addr=fetch2(pc); + pc+=2; + store(addr,a); + } +endinstr; + +instr(51,6); + sp++; +endinstr; + +HLinstr(52,11,8); + {unsigned char t=fetch(addr); + inc(t); + store(addr,t); + } +endinstr; + +HLinstr(53,11,8); + {unsigned char t=fetch(addr); + dec(t); + store(addr,t); + } +endinstr; + +HLinstr(54,10,5); + store(addr,fetch(pc)); + pc++; +endinstr; + +instr(55,4); + f=(f&0xc4)|1|(a&0x28); +endinstr; + +instr(56,7); + if(f&1)jr; + else pc++; +endinstr; + +instr(57,11); + addhl((sp>>8),(sp&0xff)); +endinstr; + +instr(58,13); + {unsigned short addr=fetch2(pc); + pc+=2; + a=fetch(addr); + } +endinstr; + +instr(59,6); + sp--; +endinstr; + +instr(60,4); + inc(a); +endinstr; + +instr(61,4); + dec(a); +endinstr; + +instr(62,7); + a=fetch(pc),pc++; +endinstr; + +instr(63,4); + f=(f&0xc4)|(cy^1)|(cy<<4)|(a&0x28); +endinstr; + +instr(0x40,4); + /* ld b,b */ +endinstr; + +instr(0x41,4); + b=c; +endinstr; + +instr(0x42,4); + b=d; +endinstr; + +instr(0x43,4); + b=e; +endinstr; + +instr(0x44,4); + b=xh; +endinstr; + +instr(0x45,4); + b=xl; +endinstr; + +HLinstr(0x46,7,8); + b=fetch(addr); +endinstr; + +instr(0x47,4); + b=a; +endinstr; + +instr(0x48,4); + c=b; +endinstr; + +instr(0x49,4); + /* ld c,c */ +endinstr; + +instr(0x4a,4); + c=d; +endinstr; + +instr(0x4b,4); + c=e; +endinstr; + +instr(0x4c,4); + c=xh; +endinstr; + +instr(0x4d,4); + c=xl; +endinstr; + +HLinstr(0x4e,7,8); + c=fetch(addr); +endinstr; + +instr(0x4f,4); + c=a; +endinstr; + +instr(0x50,4); + d=b; +endinstr; + +instr(0x51,4); + d=c; +endinstr; + +instr(0x52,4); + /* ld d,d */ +endinstr; + +instr(0x53,4); + d=e; +endinstr; + +instr(0x54,4); + d=xh; +endinstr; + +instr(0x55,4); + d=xl; +endinstr; + +HLinstr(0x56,7,8); + d=fetch(addr); +endinstr; + +instr(0x57,4); + d=a; +endinstr; + +instr(0x58,4); + e=b; +endinstr; + +instr(0x59,4); + e=c; +endinstr; + +instr(0x5a,4); + e=d; +endinstr; + +instr(0x5b,4); + /* ld e,e */ +endinstr; + +instr(0x5c,4); + e=xh; +endinstr; + +instr(0x5d,4); + e=xl; +endinstr; + +HLinstr(0x5e,7,8); + e=fetch(addr); +endinstr; + +instr(0x5f,4); + e=a; +endinstr; + +instr(0x60,4); + setxh(b); +endinstr; + +instr(0x61,4); + setxh(c); +endinstr; + +instr(0x62,4); + setxh(d); +endinstr; + +instr(0x63,4); + setxh(e); +endinstr; + +instr(0x64,4); + /* ld h,h */ +endinstr; + +instr(0x65,4); + setxh(xl); +endinstr; + +HLinstr(0x66,7,8); + h=fetch(addr); +endinstr; + +instr(0x67,4); + setxh(a); +endinstr; + +instr(0x68,4); + setxl(b); +endinstr; + +instr(0x69,4); + setxl(c); +endinstr; + +instr(0x6a,4); + setxl(d); +endinstr; + +instr(0x6b,4); + setxl(e); +endinstr; + +instr(0x6c,4); + setxl(xh); +endinstr; + +instr(0x6d,4); + /* ld l,l */ +endinstr; + +HLinstr(0x6e,7,8); + l=fetch(addr); +endinstr; + +instr(0x6f,4); + setxl(a); +endinstr; + +HLinstr(0x70,7,8); + store(addr,b); +endinstr; + +HLinstr(0x71,7,8); + store(addr,c); +endinstr; + +HLinstr(0x72,7,8); + store(addr,d); +endinstr; + +HLinstr(0x73,7,8); + store(addr,e); +endinstr; + +HLinstr(0x74,7,8); + store(addr,h); +endinstr; + +HLinstr(0x75,7,8); + store(addr,l); +endinstr; + +instr(0x76,4); + /* Was HALT - ZXCC ignores HALT */ +endinstr; + +HLinstr(0x77,7,8); + store(addr,a); +endinstr; + +instr(0x78,4); + a=b; +endinstr; + +instr(0x79,4); + a=c; +endinstr; + +instr(0x7a,4); + a=d; +endinstr; + +instr(0x7b,4); + a=e; +endinstr; + +instr(0x7c,4); + a=xh; +endinstr; + +instr(0x7d,4); + a=xl; +endinstr; + +HLinstr(0x7e,7,8); + a=fetch(addr); +endinstr; + +instr(0x7f,4); + /* ld a,a */ +endinstr; + +instr(0x80,4); + adda(b,0); +endinstr; + +instr(0x81,4); + adda(c,0); +endinstr; + +instr(0x82,4); + adda(d,0); +endinstr; + +instr(0x83,4); + adda(e,0); +endinstr; + +instr(0x84,4); + adda(xh,0); +endinstr; + +instr(0x85,4); + adda(xl,0); +endinstr; + +HLinstr(0x86,7,8); + adda(fetch(addr),0); +endinstr; + +instr(0x87,4); + adda(a,0); +endinstr; + +instr(0x88,4); + adda(b,cy); +endinstr; + +instr(0x89,4); + adda(c,cy); +endinstr; + +instr(0x8a,4); + adda(d,cy); +endinstr; + +instr(0x8b,4); + adda(e,cy); +endinstr; + +instr(0x8c,4); + adda(xh,cy); +endinstr; + +instr(0x8d,4); + adda(xl,cy); +endinstr; + +HLinstr(0x8e,7,8); + adda(fetch(addr),cy); +endinstr; + +instr(0x8f,4); + adda(a,cy); +endinstr; + +instr(0x90,4); + suba(b,0); +endinstr; + +instr(0x91,4); + suba(c,0); +endinstr; + +instr(0x92,4); + suba(d,0); +endinstr; + +instr(0x93,4); + suba(e,0); +endinstr; + +instr(0x94,4); + suba(xh,0); +endinstr; + +instr(0x95,4); + suba(xl,0); +endinstr; + +HLinstr(0x96,7,8); + suba(fetch(addr),0); +endinstr; + +instr(0x97,4); + suba(a,0); +endinstr; + +instr(0x98,4); + suba(b,cy); +endinstr; + +instr(0x99,4); + suba(c,cy); +endinstr; + +instr(0x9a,4); + suba(d,cy); +endinstr; + +instr(0x9b,4); + suba(e,cy); +endinstr; + +instr(0x9c,4); + suba(xh,cy); +endinstr; + +instr(0x9d,4); + suba(xl,cy); +endinstr; + +HLinstr(0x9e,7,8); + suba(fetch(addr),cy); +endinstr; + +instr(0x9f,4); + suba(a,cy); +endinstr; + +instr(0xa0,4); + anda(b); +endinstr; + +instr(0xa1,4); + anda(c); +endinstr; + +instr(0xa2,4); + anda(d); +endinstr; + +instr(0xa3,4); + anda(e); +endinstr; + +instr(0xa4,4); + anda(xh); +endinstr; + +instr(0xa5,4); + anda(xl); +endinstr; + +HLinstr(0xa6,7,8); + anda(fetch(addr)); +endinstr; + +instr(0xa7,4); + anda(a); +endinstr; + +instr(0xa8,4); + xora(b); +endinstr; + +instr(0xa9,4); + xora(c); +endinstr; + +instr(0xaa,4); + xora(d); +endinstr; + +instr(0xab,4); + xora(e); +endinstr; + +instr(0xac,4); + xora(xh); +endinstr; + +instr(0xad,4); + xora(xl); +endinstr; + +HLinstr(0xae,7,8); + xora(fetch(addr)); +endinstr; + +instr(0xaf,4); + xora(a); +endinstr; + +instr(0xb0,4); + ora(b); +endinstr; + +instr(0xb1,4); + ora(c); +endinstr; + +instr(0xb2,4); + ora(d); +endinstr; + +instr(0xb3,4); + ora(e); +endinstr; + +instr(0xb4,4); + ora(xh); +endinstr; + +instr(0xb5,4); + ora(xl); +endinstr; + +HLinstr(0xb6,7,8); + ora(fetch(addr)); +endinstr; + +instr(0xb7,4); + ora(a); +endinstr; + +instr(0xb8,4); + cpa(b); +endinstr; + +instr(0xb9,4); + cpa(c); +endinstr; + +instr(0xba,4); + cpa(d); +endinstr; + +instr(0xbb,4); + cpa(e); +endinstr; + +instr(0xbc,4); + cpa(xh); +endinstr; + +instr(0xbd,4); + cpa(xl); +endinstr; + +HLinstr(0xbe,7,8); + cpa(fetch(addr)); +endinstr; + +instr(0xbf,4); + cpa(a); +endinstr; + +instr(0xc0,5); + if(!(f&0x40))ret; +endinstr; + +instr(0xc1,10); + pop1(b,c); +endinstr; + +instr(0xc2,10); + if(!(f&0x40))jp; + else pc+=2; +endinstr; + +instr(0xc3,10); + jp; +endinstr; + +instr(0xc4,10); + if(!(f&0x40))call; + else pc+=2; +endinstr; + +instr(0xc5,11); + push1(b,c); +endinstr; + +instr(0xc6,7); + adda(fetch(pc),0); + pc++; +endinstr; + +instr(0xc7,11); + push2(pc); + pc=0; +endinstr; + +instr(0xc8,5); + if(f&0x40)ret; +endinstr; + +instr(0xc9,4); + ret; +endinstr; + +instr(0xca,10); + if(f&0x40)jp; + else pc+=2; +endinstr; + +instr(0xcb,4); +#include "cbops.h" +endinstr; + +instr(0xcc,10); + if(f&0x40)call; + else pc+=2; +endinstr; + +instr(0xcd,10); + call; +endinstr; + +instr(0xce,7); + adda(fetch(pc),cy); + pc++; +endinstr; + +instr(0xcf,11); + push2(pc); + pc=8; +endinstr; + +instr(0xd0,5); + if(!cy)ret; +endinstr; + +instr(0xd1,10); + pop1(d,e); +endinstr; + +instr(0xd2,10); + if(!cy)jp; + else pc+=2; +endinstr; + +instr(0xd3,11); + tstates+=out(tstates,a,fetch(pc),a); + pc++; +endinstr; + +instr(0xd4,10); + if(!cy)call; + else pc+=2; +endinstr; + +instr(0xd5,11); + push1(d,e); +endinstr; + +instr(0xd6,7); + suba(fetch(pc),0); + pc++; +endinstr; + +instr(0xd7,11); + push2(pc); + pc=16; +endinstr; + +instr(0xd8,5); + if(cy)ret; +endinstr; + +instr(0xd9,4); + swap(b,b1); + swap(c,c1); + swap(d,d1); + swap(e,e1); + swap(h,h1); + swap(l,l1); +endinstr; + +instr(0xda,10); + if(cy)jp; + else pc+=2; +endinstr; + +instr(0xdb,11); + {unsigned short t; + a=t=in(tstates,a,fetch(pc)); + tstates+=t>>8; + pc++; + } +endinstr; + +instr(0xdc,10); + if(cy)call; + else pc+=2; +endinstr; + +instr(0xdd,4); + new_ixoriy=1; + intsample=0; +endinstr; + +instr(0xde,7); + suba(fetch(pc),cy); + pc++; +endinstr; + +instr(0xdf,11); + push2(pc); + pc=24; +endinstr; + +instr(0xe0,5); + if(!(f&4))ret; +endinstr; + +instr(0xe1,10); + if(!ixoriy)pop1(h,l); + else if(ixoriy==1)pop2(ix); + else pop2(iy); +endinstr; + +instr(0xe2,10); + if(!(f&4))jp; + else pc+=2; +endinstr; + +instr(0xe3,19); + if(!ixoriy){ + unsigned short t=fetch2(sp); + store2b(sp,h,l); + l=t; + h=t>>8; + } + else if(ixoriy==1){ + unsigned short t=fetch2(sp); + store2(sp,ix); + ix=t; + } + else{ + unsigned short t=fetch2(sp); + store2(sp,iy); + iy=t; + } +endinstr; + +instr(0xe4,10); + if(!(f&4))call; + else pc+=2; +endinstr; + +instr(0xe5,11); + if(!ixoriy)push1(h,l); + else if(ixoriy==1)push2(ix); + else push2(iy); +endinstr; + +instr(0xe6,7); + anda(fetch(pc)); + pc++; +endinstr; + +instr(0xe7,11); + push2(pc); + pc=32; +endinstr; + +instr(0xe8,5); + if(f&4)ret; +endinstr; + +instr(0xe9,4); + pc=!ixoriy?hl:ixoriy==1?ix:iy; +endinstr; + +instr(0xea,10); + if(f&4)jp; + else pc+=2; +endinstr; + +instr(0xeb,4); + swap(h,d); + swap(e,l); +endinstr; + +instr(0xec,10); + if(f&4)call; + else pc+=2; +endinstr; + +instr(0xed,4); +#include"edops.h" +endinstr; + +instr(0xee,7); + xora(fetch(pc)); + pc++; +endinstr; + +instr(0xef,11); + push2(pc); + pc=40; +endinstr; + +instr(0xf0,5); + if(!(f&0x80))ret; +endinstr; + +instr(0xf1,10); + pop1(a,f); +endinstr; + +instr(0xf2,10); + if(!(f&0x80))jp; + else pc+=2; +endinstr; + +instr(0xf3,4); + iff1=iff2=0; + intsample=0; +endinstr; + +instr(0xf4,10); + if(!(f&0x80))call; + else pc+=2; +endinstr; + +instr(0xf5,11); + push1(a,f); +endinstr; + +instr(0xf6,7); + ora(fetch(pc)); + pc++; +endinstr; + +instr(0xf7,11); + push2(pc); + pc=48; +endinstr; + +instr(0xf8,5); + if(f&0x80)ret; +endinstr; + +instr(0xf9,6); + sp=!ixoriy?hl:ixoriy==1?ix:iy; +endinstr; + +instr(0xfa,10); + if(f&0x80)jp; + else pc+=2; +endinstr; + +instr(0xfb,4); + iff1=iff2=1; + intsample=0; +endinstr; + +instr(0xfc,10); + if(f&0x80)call; + else pc+=2; +endinstr; + +instr(0xfd,4); + new_ixoriy=2; + intsample=0; +endinstr; + +instr(0xfe,7); + cpa(fetch(pc)); + pc++; +endinstr; + +instr(0xff,11); + push2(pc); + pc=56; +endinstr; + diff --git a/Tools/unix/zx/zx.c b/Tools/unix/zx/zx.c new file mode 100644 index 00000000..0ea315d0 --- /dev/null +++ b/Tools/unix/zx/zx.c @@ -0,0 +1,437 @@ +#include "zx.h" + +#ifdef WIN32 +#include "windows.h" +#endif + +/* Global variables */ + +char *progname; +char **argv; +int argc; + +byte cpm_drive; +char *mypath; + +byte cpm_user; +extern byte cpm_error; + +byte RAM[65536]; /* The Z80's address space */ + +void load_comfile(void); /* Forward declaration */ + +static int deinit_term, deinit_gsx; + +void dump_regs(FILE *fp, byte a, byte b, byte c, byte d, byte e, byte f, + byte h, byte l, word pc, word ix, word iy) +{ + fprintf(fp, "\tAF=%02x%02x BC=%02x%02x DE=%02x%02x HL=%02x%02x\n" + "\tIX=%04x IY=%04x PC=%04x\n", + a,f,b,c,d,e,h,l,pc,ix,iy); +} + + + +char *parse_to_fcb(char *s, int afcb) +{ + byte *fcb = &RAM[afcb+1]; + + RAM[afcb] = 0; + memset(fcb, ' ', 11); + + while (1) + { + if (s[0] == 0) break; + if (s[0] == ' ') {++s; continue; } + if (s[1] == ':') + { + RAM[afcb] = s[0] - '@'; + if (RAM[afcb] > 16) RAM[afcb] -= 0x20; + s+=2; + continue; + } + if (s[0] == '.') + { + ++s; + fcb = &RAM[afcb+9]; + continue; + } + *fcb = *s; if (islower(*fcb)) *fcb = toupper(*fcb); + ++s; + ++fcb; + if (fcb >= &RAM[afcb+12]) break; + } + return s; +} + + +void Msg(char *s, ...) +{ +#ifdef DEBUG + va_list ap; + + va_start(ap, s); + printf("%s trace: ", progname); + vprintf(s, ap); + fflush(stdout); + if (s[strlen(s) - 1] == '\n') putchar('\r'); + va_end(ap); +#endif +} + + +void ed_fe(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy) +{ + switch(*a) + { + case 0xC0: + cpmbdos(a,b,c,d,e,f,h,l,pc,ix,iy); + break; + + case 0xC1: + load_comfile(); + break; + + case 0xC2: + fprintf(stderr,"%s: Incompatible BIOS.BIN\n", progname); + zx_term(); + zx_exit(1); + + case 0xC3: + cpmbios(a,b,c,d,e,f,h,l,pc,ix,iy); + break; + + default: + fprintf(stderr, "%s: Z80 encountered invalid trap\n", progname); + dump_regs(stderr,*a,*b,*c,*d,*e,*f,*h,*l,*pc,*ix,*iy); + zx_term(); + zx_exit(1); + + } +} + + +/* + * load_bios() loads the minimal CP/M BIOS and BDOS. + * + */ + +void load_bios(void) +{ + int bios_len; + + FILE * fp = NULL; + char biospath[CPM_MAXPATH + 1] = ""; + +#ifdef WIN32 + if (!fp) + { + GetModuleFileName(NULL, biospath, sizeof(biospath)); + strcpy(strrchr(biospath, '\\'), "\\bios.bin"); + fp = fopen(biospath, "rb"); + } +#else + if (!fp) { + strcpy(biospath, mypath); + strcpy(strrchr(biospath, '/'), "/bios.bin"); + fp = fopen(biospath, "rb"); + } +#endif + + if (!fp && BINDIR80) + { + strcpy(biospath, BINDIR80); + strcat(biospath, "bios.bin"); + fp = fopen(biospath, "rb"); + } + + if (!fp) fp = fopen("bios.bin", "rb"); + + if (!fp) + { + fprintf(stderr,"%s: Cannot locate bios.bin\n", progname); + zx_term(); + zx_exit(1); + } + bios_len = fread(RAM + 0xFE00, 1, 512, fp); + if (bios_len < 1 || ferror(fp)) + { + fclose(fp); + fprintf(stderr,"%s: Cannot load bios.bin\n", progname); + zx_term(); + zx_exit(1); + } + fclose(fp); + + Msg("Loaded %d bytes of BIOS\n", bios_len); +} + +/* + * try_com() attempts to open file, file.com, file.COM, file.cpm and file.CPM + * + */ + +FILE *try_com(char *s) +{ + char fname[CPM_MAXPATH + 1]; + FILE *fp; + + strcpy(fname, s); + fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.com", fname); fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.COM", fname); fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.cpm", fname); fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.CPM", fname); fp = fopen(s, "rb"); if (fp) return fp; + + strcpy(s, fname); + return NULL; +} + +/* + * load_comfile() loads the COM file whose name was passed as a parameter. + * + */ + + +void load_comfile(void) +{ + int com_len; + char fname[CPM_MAXPATH + 1] = ""; + FILE *fp; + + if (BINDIR80) strcpy(fname, BINDIR80); + strcat(fname, argv[1]); + fp = try_com(fname); + if (!fp) + { + strcpy(fname, argv[1]); + fp = try_com(fname); + } + if (!fp) + { + fprintf(stderr,"%s: Cannot locate %s, %s.com, %s.COM, %s.cpm _or_ %s.CPM\r\n", + progname, argv[1], argv[1], argv[1], argv[1], argv[1]); + zx_term(); + zx_exit(1); + } + com_len = fread(RAM + 0x0100, 1, 0xFD00, fp); + if (com_len < 1 || ferror(fp)) + { + fclose(fp); + fprintf(stderr,"%s: Cannot load %s\n", progname, fname); + zx_term(); + zx_exit(1); + } + fclose(fp); + + Msg("Loaded %d bytes from %s\n", com_len, fname); +} + +unsigned int in() { return 0; } +unsigned int out() { return 0; } + + + +/* + * xltname: Convert a unix filepath into a CP/M compatible drive:name form. + * The unix filename must be 8.3 or the CP/M code will reject it. + * + * This uses the library xlt_name to do the work, and then just strcat()s + * the result to the command line. + */ + +void zx_xltname(char *name, char *pcmd) +{ + char nbuf[CPM_MAXPATH + 1]; + + xlt_name(pcmd, nbuf); + + strcat(name, nbuf); +} + +/* main() parses the arguments to CP/M form. argv[1] is the name of the CP/M + program to load; the remaining arguments are arguments for the CP/M program. + + main() also loads the vestigial CP/M BIOS and does some sanity checks + on the endianness of the host CPU and the sizes of data types. + */ + +int main(int ac, char **av) +{ + int n; + char *pCmd, *str; + + argc = ac; + argv = av; +#ifdef __PACIFIC__ /* Pacific C doesn't support argv[0] */ + progname="ZX"; +#endif + progname = argv[0]; + mypath = strdup(argv[0]); + + /* DJGPP includes the whole path in the program name, which looks + * untidy... + */ + str = strrchr(progname, '/'); + if (!str) str = strrchr(progname, '\\'); + if (str) progname = str + 1; + + if (_isatty(_fileno(stdin))) + Msg("Using interactive console mode\n"); + else + Msg("Using standard input/ouput mode\n"); + + if (sizeof(int) > 8 || sizeof(byte) != 1 || sizeof(word) != 2) + { + fprintf(stderr,"%s: type lengths incorrect; edit typedefs " + "and recompile.\n", progname); + zx_exit(1); + } + + if (argc < 2) + { + fprintf(stderr,"%s: No CP/M program name provided.\n",progname); + zx_exit(1); + } + + + setmode(_fileno(stdin), O_BINARY ); + setmode(_fileno(stdout), O_BINARY ); + + /* Parse arguments. An argument can be either: + + * preceded by a '-', in which case it is copied in as-is, less the + dash; + * preceded by a '+', in which case it is parsed as a filename and + then concatenated to the previous argument; + * preceded by a '+-', in which case it is concatenated without + parsing; + * not preceded by either, in which case it is parsed as a filename. + + So, the argument string "--a -b c +-=q --x +/dev/null" would be rendered + into CP/M form as "-a b p:c=q -xd:null" */ + + if (!fcb_init()) + { + fprintf(stderr, "Could not initialise CPMREDIR library\n"); + zx_exit(1); + } + + xlt_map(0, BINDIR80); /* Establish the 3 fixed mappings */ + xlt_map(1, LIBDIR80); + xlt_map(2, INCDIR80); + pCmd = (char *)RAM + 0x81; + + for (n = 2; n < argc; n++) + { + if (argv[n][0] == '+' && argv[n][1] == '-') + { + /* Append, no parsing */ + strcat(pCmd, argv[n] + 2); + } + else if (!argv[n][0] || argv[n][0] == '-') + { + /* Append with space; no parsing. */ + strcat(pCmd, " "); + strcat(pCmd, argv[n] + 1); + } + else if (argv[n][0] == '+') + { + zx_xltname(pCmd, argv[n]+1); + } + else /* Translate a filename */ + { + strcat(pCmd, " "); + zx_xltname(pCmd, argv[n]); + } + + } + pCmd[0x7F] = 0; /* Truncate to fit the buffer */ + RAM[0x80] = strlen(pCmd); + + str = parse_to_fcb(pCmd, 0x5C); + parse_to_fcb(str, 0x6C); + +/* This statement is very useful when creating a client like zxc or zxas + + printf("Command tail is %s\n", pCmd); +*/ + load_bios(); + + memset(RAM + 0xFE9C, 0, 0x64); /* Zap the SCB */ + RAM[0xFE98] = 0x06; + RAM[0xFE99] = 0xFE; /* FE06, BDOS entry */ + RAM[0xFEA1] = 0x31; /* BDOS 3.1 */ + RAM[0xFEA8] = 0x01; /* UK date format */ + RAM[0xFEAF] = 0x0F; /* CCP drive */ + +#ifdef USE_CPMIO + RAM[0xFEB6] = cpm_term_direct(CPM_TERM_WIDTH, -1); + RAM[0xFEB8] = cpm_term_direct(CPM_TERM_HEIGHT, -1); +#else + RAM[0xFEB6] = 79; + RAM[0xFEB8] = 23; +#endif + RAM[0xFED1] = 0x80; /* Buffer area */ + RAM[0xFED2] = 0xFF; + RAM[0xFED3] = '$'; + RAM[0xFED6] = 0x9C; + RAM[0xFED7] = 0xFE; /* SCB address */ + RAM[0xFED8] = 0x80; /* DMA address */ + RAM[0xFED9] = 0x00; + RAM[0xFEDA] = 0x0F; /* P: */ + RAM[0xFEE6] = 0x01; /* Multi sector count */ + RAM[0xFEFE] = 0x06; + RAM[0xFEFF] = 0xFE; /* BDOS */ + + cpm_drive = 0x0F; /* Start logged into P: */ + cpm_user = 0; /* and user 0 */ + +#ifdef USE_CPMIO + cpm_scr_init(); deinit_term = 1; +#endif +#ifdef USE_CPMGSX + gsx_init(); deinit_gsx = 1; +#endif + + /* Start the Z80 at 0xFF00, with stack at 0xFE00 */ + mainloop(0xFF00, 0xFE00); + + return zx_term(); +} + +void zx_exit(int code) +{ +#ifdef USE_CPMIO + if (deinit_term) cpm_scr_unit(); +#endif +#ifdef USE_CPMGSX + if (deinit_gsx) gsx_deinit(); +#endif + exit(code); +} + +int zx_term(void) +{ + word n; + + + n = RAM[0x81]; /* Get the return code. This is Hi-Tech C */ + n = (n << 8) | RAM[0x80]; /* specific and fails with other COM files */ + + putchar('\n'); + + if (cpm_error != 0) /* The CP/M "set return code" call was used */ + { /* (my modified Hi-Tech C library uses this */ + n = cpm_error; /* call) */ + } + if (n < 256 || n == 0xFFFF) + { + Msg("Return code %d\n", n); + return n; + } + else return 0; +} + + diff --git a/Tools/unix/zx/zx.h b/Tools/unix/zx/zx.h new file mode 100644 index 00000000..c2fd0930 --- /dev/null +++ b/Tools/unix/zx/zx.h @@ -0,0 +1,95 @@ +/* + * Change the directories in these #defines if necessary. Note trailing slash. + */ + +#include "config.h" + +#ifdef __MSDOS__ + #define BINDIR80 "d:/tools/cpm/bin80/" + #define LIBDIR80 "d:/tools/cpm/lib80/" + #define INCDIR80 "d:/tools/cpm/include/" +#else +/* Unless overridden, these are defined by autoconf. Note trailing slash. + #undef BINDIR80 + #undef LIBDIR80 + #undef INCDIR80 + #define BINDIR80 "/usr/local/lib/cpm/bin80/" + #define LIBDIR80 "/usr/local/lib/cpm/lib80/" + #define INCDIR80 "/usr/local/lib/cpm/include80/" +*/ +#endif + +#define SERIAL "ZXCC05" + +/* System include files */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef LINUX +#include +#define _isatty(a) isatty(a) +#define _fileno(a) fileno(a) +#define setmode(a,b) +#define O_BINARY 0 +#endif +#ifdef WIN32 +#include +#endif +#include +#include +#ifdef __MSDOS +#include +#endif + +/* Library includes */ + +#ifdef USE_CPMIO +#include "cpmio.h" +#endif + +#ifdef USE_CPMGSX +#include "cpmgsx.h" +#endif + +#include "cpmredir.h" /* BDOS disc simulation */ + +typedef unsigned char byte; /* Must be exactly 8 bits */ +typedef unsigned short word; /* Must be exactly 16 bits */ + +/* Prototypes */ + +void ed_fe (byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy); +void cpmbdos(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy); +void cpmbios(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy); +void dump_regs(FILE *fp, byte a, byte b, byte c, byte d, byte e, byte f, + byte h, byte l, word pc, word ix, word iy); +void Msg(char *s, ...); +int zx_term(void); +void zx_exit(int code); + +byte cin(void); +void cout(byte); +int cstat(void); + +/* Global variables */ + +extern char *progname; +extern char **argv; +extern int argc; +extern byte RAM[65536]; /* The Z80's address space */ + +extern usestdio; + +/* Z80 CPU emulation */ + +#include "z80.h" + diff --git a/Tools/unix/zx/zx.html b/Tools/unix/zx/zx.html new file mode 100644 index 00000000..b425ee1e --- /dev/null +++ b/Tools/unix/zx/zx.html @@ -0,0 +1,131 @@ +zx CP/M Command Line Emulator + +

zx CP/M Command Line Emulator

+ +

zx allows execution of CP/M 2.2 and 3.X application from a +Windows command line. It is compatible with Windows XP and greater (both +32 and 64 bit).

+ +

zx is basically a port of a subset of the zxcc package by John Elliott. +The GPLv2 licensing carries forward. Please refer to the + +zxcc web page for more information.

+ +

While the original zxcc package was generally intended to allow +execution of the Hi-Tech C CP/M compiler under Unix, zx is slightly +more general and intended to allow running most CP/M tools. Specific +changes were incorporated to improve interactice console operation of +CP/M applications.

+ +

Setup

+ +

The zx application (zx.exe) may be copied to any directory for execution. +The bios.bin file must be copied to the same directory. For ease of use, +you will probably want the directory to part of your PATH environment +variable so that you can run the tool from any location.

+ +

You will also need the CP/M applications that you want to run. +zx will load files fromthe current directory or one of the following +directories based on file type. Any of the following environment +variables may be defined to determine where zx searches for the +respective file types:

+ +
    +
  • ZXBINDIR may contain a single path which will +be searched for executable files (usually *.com)
  • +
  • ZXLIBDIR may contain a single path which will +be search for library files (usually *.lib)
  • +
  • ZXINCDIR may contain a single path which will +be searched for include files (usually *.inc)
  • +
+ +

Usage

+ +

In general CP/M applications are executed by prefixing the CP/M command +line with "zx". So for example, you could assemble a test.asm using +rmac with a command line like:

+ +
zx rmac hello
+ +

In this case, rmac.com would need to be in the directory specified by +environment variable ZXBINDIR or in the current directory. Also, +hello.asm would need to be in the current directory.

+ +

Filenames

+ +

Where you would normally enter a CP/M filename you instead enter +a Windows filename. Note that you will need to use a forward slash +instead of the traditional backslash as a directory separator. The +filename itself (as opposed to any directories in +its path) must obey CP/M 8.3 naming conventions.

+ +

Where the documentation requires a CP/M drive letter/user number +you should enter a path complete with trailing slash, for example:

+
-I/usr/src/linux-80/include/
+ +

Technical

+ +

zx emulates a subset of CP/M 3; hopefully enough to run the +most CP/M tools. It can be used as a limited general-purpose CP/M 3 +emulator provided the emulated program only uses a common subset of +system calls.

+ +

Syntax for zx is:

+ +
+zx comfile.com arg1 arg2 ... +
+ +

The comfile is the program to run; zx searches the current +directory and ZXBINDIR for it.

+ +

The arguments are parsed in this way:

+ +
    +
  • Any argument starting with a - sign is passed to the CP/M program as-is, +minus the leading - sign. +
  • Any argument starting with a + sign is parsed as a filename (see below) +and then concatenated to the previous argument. +
  • Any argument starting "+-" is concatenated without being parsed. +
  • All other arguments are parsed as filenames. The path is +converted to a CP/M driveletter. +
+ +

For example: +

+zx foo.com --Q -A /src/main --I +/src/sub +-, +/foo/bar +
+ +

would pass these arguments to foo.com:

+ +
+-Q A d:main -Id:sub,e:bar +
+ +

The other programs are merely wrappers that convert their command lines +into the form required by zx.

+ +

Errors

+ +

Any errors raised by the zx runtime system will be prefixed +with zx:. Some errors you may encounter are:

+ +
+
Unsupported BDOS call
+
Part of CP/M 3 that the program uses has not been emulated. Add the +required functionality to zxbdos.c and recompile.
+
Z80 encountered invalid trap
+
The CP/M program being run attempted to call the zx runtime +system with an unknown call number.
+
+ +

Acknowledgements

+ +
    +
  • zxcc was written by John Elliott
  • +
  • Hi-Tech C was written by Hi-Tech Software.
  • +
  • The Z80 emulation engine was written by Ian Collier.
  • +
  • Thanks to Jacob Nevins, Andy Parkins and others for bug fix suggestions.
  • +
+ + diff --git a/Tools/unix/zx/zxbdos.c b/Tools/unix/zx/zxbdos.c new file mode 100644 index 00000000..06b951ad --- /dev/null +++ b/Tools/unix/zx/zxbdos.c @@ -0,0 +1,561 @@ +#include "zx.h" + +#define BDOS_DEF +#include "zxbdos.h" +#include "zxcbdos.h" +#include "zxdbdos.h" + +#ifdef __MSDOS__ +#include +#endif + +#define BCD(x) (((x % 10)+16*(x/10)) & 0xFF) + +/* Convert time_t to CP/M day count/hours/minutes */ +dword cpmtime(time_t t) +{ + long d = (t / 86400) - 2921; /* CP/M day 0 is unix day 2921 */ + long h = (t % 86400) / 3600; /* Hour, 0-23 */ + long m = (t % 3600) / 60; /* Minute, 0-59 */ + + return (d | (BCD(h) << 16) | (BCD(m) << 24)); +} + + +byte get_time(cpm_word b) +{ + time_t t; + + time(&t); + wr32(b, cpmtime(t)); + + return (BCD(t % 60)); +} + + +/* Functions to access 24-bit & 32-bit words in memory. These are always + little-endian. */ + +void wr24(word addr, dword v) +{ + RAM[addr ] = v & 0xFF; + RAM[addr + 1] = (v >> 8) & 0xFF; + RAM[addr + 2] = (v >> 16) & 0xFF; +} + +void wr32(word addr, dword v) +{ + RAM[addr ] = v & 0xFF; + RAM[addr + 1] = (v >> 8) & 0xFF; + RAM[addr + 2] = (v >> 16) & 0xFF; + RAM[addr + 3] = (v >> 24) & 0xFF; +} + +dword rd24(word addr) +{ + register dword rv = RAM[addr + 2]; + + rv = (rv << 8) | RAM[addr + 1]; + rv = (rv << 8) | RAM[addr]; + return rv; +} + + +dword rd32(word addr) +{ + register dword rv = RAM[addr + 3]; + + rv = (rv << 8) | RAM[addr + 2]; + rv = (rv << 8) | RAM[addr + 1]; + rv = (rv << 8) | RAM[addr]; + return rv; +} + +#define peekw(addr) ( (((word)(RAM[addr + 1])) << 8) | RAM[addr]) + + +/* Get / set the program return code. We store this in 'C' form: 0 for + success, 1-255 for failure. Translate to/from the CP/M form of: + + 0x0000-0xFEFF for success + 0xFF00-0xFFFE for failure + + We also store the actual value so it can be returned + + */ + +word cpm_errcde(word DE) +{ + static word real_err = 0; + + if (DE == 0xFFFF) return real_err; + real_err = DE; + + if (DE == 0xFF00) cpm_error = 1; + else if (DE > 0xFF00) cpm_error = (DE & 0xFF); + else cpm_error = 0; + return 0; +} + + +#ifdef USE_CPMGSX +gsx_byte gsxrd(gsx_word addr) +{ + return RdZ80(addr); +} + +void gsxwr(gsx_word addr, gsx_byte value) +{ + WrZ80(addr, value); +} + +#endif + +#undef bc +#undef de +#undef hl + +void setw(byte *l, byte *h, word w) +{ + *l = (w & 0xFF); + *h = (w >> 8) & 0xFF; +} + +void cpmbdos(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy) +{ + word de = ((*d) << 8) | *e; + word hl = ((*h) << 8) | *l; + byte *pde = &RAM[de]; + byte *pdma = &RAM[cpm_dma]; + word temp; + int retv; + + Msg("BDOS service invoked: C=%02x DE=%04x\n", *c, de); + + switch(*c) + { + case 0: + *pc = 0; + break; + + case 1: /* Get a character */ +#ifdef USE_CPMIO + retv = cpm_bdos_1(); +#else + retv = cin(); +#endif + if (retv < 0) *pc = 0; + setw(l, h, retv); + break; + + case 2: /* Print a character */ +#ifdef USE_CPMIO + if (cpm_bdos_2(*e)) *pc = 0; +#else + cout(*e); +#endif + break; + + case 3: /* No auxin */ + setw(l, h, 0x1A); + break; + + case 4: /* No auxout */ + break; + + case 5: /* No printer */ + break; + + case 6: /* Direct console I/O */ + retv = cpm_bdos_6(*e); + if (retv < 0) *pc = 0; + setw(l, h, retv); + break; + + case 7: /* No auxist */ + case 8: /* No auxost */ + break; + + case 9: /* Print a $-terminated string */ +#ifdef USE_CPMIO + if (cpm_bdos_9((char *)pde)) *pc = 0; +# else + for (temp = 0; RAM[de + temp] != '$'; ++temp) + { + cout(RAM[de + temp]); + } +#endif + break; + + case 0x0A: + bdos_rdline(de, &(*pc)); + break; + + case 0x0B: /* Console status */ + //*l = *h = 0; /* No keys pressed */ + *l = cstat(); + *h = 0; + break; + + case 0x0C: /* Get CP/M version */ + +/* For GENCOM's benefit, claim to be v3.1 */ + + *l = 0x31; /* v3.1 */ + //*l = 0x22; /* v2.2 */ + *h = 0; /* CP/M, no network */ + break; + + case 0x0D: /* Re-log discs */ + fcb_reset(); + break; + + case 0x0E: /* Set default drive */ + setw(l, h, fcb_drive(*e)); + break; + + case 0x0F: /* Open using FCB */ + setw(l, h, x_fcb_open(pde, pdma)); + break; + + case 0x10: /* Close using FCB */ + setw(l, h, fcb_close(pde)); + break; + + case 0x11: /* Find first */ + setw(l, h, fcb_find1(pde, pdma)); + break; + + case 0x12: + setw(l, h, fcb_find2(pde, pdma)); + break; + + case 0x13: /* Delete using FCB */ + setw(l, h, fcb_unlink(pde, pdma)); + break; + + case 0x14: /* Sequential read using FCB */ + setw(l, h, fcb_read(pde, pdma)); + + //Msg("fcb_read L=%02x H=%02x\n", *l, *h); + + break; + + case 0x15: /* Sequential write using FCB */ + setw(l, h, fcb_write(pde, pdma)); + break; + + case 0x16: /* Create using FCB */ + setw(l, h, fcb_creat(pde, pdma)); + break; + + case 0x17: /* Rename using FCB */ + setw(l, h, fcb_rename(pde, pdma)); + break; + + case 0x18: /* Get login vector */ + setw(l, h, fcb_logvec()); + break; + + case 0x19: /* Get default drive */ + setw(l, h, cpm_drive); + break; + + case 0x1A: /* Set DMA */ + Msg("Set DMA to %04x\n", de); + cpm_dma = de; + break; + + case 0x1B: /* Get alloc vector */ + fcb_getalv(RAM + 0xFF80, 0x40); + setw(l, h, 0xFF80); + break; + + case 0x1C: /* Make disc R/O */ + setw(l, h, fcb_rodisk()); + break; + + case 0x1D: /* Get R/O vector */ + setw(l, h, fcb_rovec()); + break; + + case 0x1E: /* Set attributes */ + setw(l, h, fcb_chmod(pde, pdma)); + break; + + case 0x1F: /* Get DPB */ + fcb_getdpb(RAM + 0xFFC0); + setw(l, h, 0xFFC0); + break; /* Whoops. Missed that 'break'. */ + + case 0x20: /* Get/set uid */ + setw(l, h, fcb_user(*e)); + break; + + case 0x21: /* Read a record */ + setw(l, h, fcb_randrd(pde, pdma)); + break; + + case 0x22: /* Write a record */ + setw(l, h, fcb_randwr(pde, pdma)); + break; + + case 0x23: /* Get file size */ + setw(l, h, x_fcb_stat(pde)); + break; + + case 0x24: /* Get file pointer */ + setw(l, h, fcb_tell(pde)); + break; + + case 0x25: + setw(l, h, fcb_resro(de)); + break; + + /* MP/M drive access functions, not implemented */ + + case 0x28: /* Write with 0 fill */ + setw(l, h, fcb_randwz(pde, pdma)); + break; + + /* MP/M record locking functions, not implemented */ + + case 0x2C: /* Set no. of records to read/write */ + setw(l, h, fcb_multirec(*e)); + break; + + case 0x2D: /* Set error mode */ + err_mode = *e; + break; + + case 0x2E: + setw(l, h, fcb_dfree(*e, pdma)); + break; /* Whoops. Missed that 'break'. */ + + /* 0x2F: Chain */ + + case 0x30: + setw(l, h, fcb_sync(*e)); + break; + + case 0x31: + if (pde[1] == 0xFE) + { + RAM[0xFE9C + *pde] = pde[2]; + RAM[0xFE9D + *pde] = pde[3]; + } + else if (RAM[hl + 1] == 0xFF) + { + RAM[0xFE9C + *pde] = pde[2]; + } + else + { + *l = RAM[0xFE9C + *pde]; + *h = RAM[0xFE9D + *pde]; + } + break; + + case 0x32: + temp = *ix; + *ix = 3 * (pde[0] + 1); + *a = pde[1]; + *c = pde[2]; + *b = pde[3]; + *e = pde[4]; + *d = pde[5]; + *l = pde[6]; + *h = pde[7]; + cpmbios(a,b,c,d,e,f,h,l,pc,ix,iy); + *ix = temp; + break; + + case 0x3C: /* Communicate with RSX */ + *h = *l = 0; + break; + + case 0x62: /* Purge */ + setw(l, h, fcb_purge()); + break; + + case 0x63: /* Truncate file */ + setw(l, h, fcb_trunc(pde, pdma)); + break; + + case 0x64: /* Set label */ + setw(l, h, fcb_setlbl(pde, pdma)); + break; + + case 0x65: /* Get label byte */ + setw(l, h, fcb_getlbl(*e)); + break; + + case 0x66: /* Get file date */ + setw(l, h, fcb_date(pde)); + break; + + case 0x67: /* Set password */ + setw(l, h, fcb_setpwd(pde, pdma)); + break; + + case 0x68: /* Set time of day */ + /* Not advisable to let an emulator play with the clock */ + break; + + case 0x69: /* Get time of day */ + setw(l, h, get_time(de)); + break; + + case 0x6A: /* Set default password */ + setw(l, h, fcb_defpwd(pde)); + break; + + case 0x6B: /* Get serial number */ + memcpy(pde, SERIAL, 6); + break; + + case 0x6C: /* 0.03 set error code */ + setw(l, h, cpm_errcde(de)); + break; + +#ifdef USE_CPMIO + case 0x6D: /* Set/get console mode */ + setw(l, h, cpm_bdos_109(de)); + break; + + case 0x6E: /* Set/get string delimiter */ + setw(l, h, cpm_bdos_110(*e)); + break; + + case 0x6F: /* Send fixed length string to screen */ + if (cpm_bdos_111((char *)RAM + peekw(de), + peekw(de + 2))) + *pc = 0; + break; + + case 0x70: /* Send fixed length string to printer */ + break; + + /* 0x71: Strange PCP/M function */ +#else + case 0x6D: /* Set/get console mode */ + setw(l, h, 0); + break; + +#endif + +#ifdef USE_CPMGSX + case 0x73: /* GSX */ + setw(l, h, gsx80(gsxrd, gsxwr, de)); + break; +#endif + + case 0x74: /* Set date stamp */ + setw(l, h, fcb_sdate(pde, pdma)); + break; + + case 0x98: /* Parse filename */ + setw(l, h, fcb_parse((char *)RAM + peekw(de), + (byte *)RAM + peekw(de + 2))); + break; + + default: +#ifdef USE_CPMIO + cpm_scr_unit(); +#endif +#ifdef USE_CPMGSX + gsx_deinit(); +#endif + + fprintf(stderr,"%s: Unsupported BDOS call %d\n", progname, + (int)(*c)); + dump_regs(stderr,*a,*b,*c,*d,*e,*f,*h,*l,*pc,*ix,*iy); + zx_exit(1); + break; + } + + *a = *l; + *b = *h; + + Msg("BDOS service completion.\n"); +} + + + +void cpmbios(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy) +{ + int func = (((*ix) & 0xFF) / 3) - 1; + + Msg("BIOS service invoked: func=%02x\n", func); + + switch(func) /* BIOS function */ + { + case 1: + zx_exit(zx_term()); /* Program termination */ + break; + + case 2: /* CONST */ +#ifdef USE_CPMIO + *a = cpm_const(); +#else + *a = cpm_bdos_6(0xFE); +#endif + break; + + case 3: /* CONIN */ +#ifdef USE_CPMIO + *a = cpm_conin(); +#else + *a = cpm_bdos_6(0xFD); +#endif + break; + + case 4: /* CONOUT */ +#ifdef USE_CPMIO + cpm_conout(*c); +#else + cpm_bdos_6(*c); +#endif + break; + + case 20: /* DEVTBL */ + setw(l, h, 0xFFFF); + break; + + case 22: /* DRVTBL */ + setw(l, h, 0xFFFF); + break; + + case 26: /* TIME */ + RAM[0xFEF8] = get_time(0xFEF4); + break; + + case 30: /* USERF!!! */ +#ifdef USE_CPMIO + cpm_bdos_110('$'); + cpm_bdos_9("This program has attempted to call USERF, " + "which is not implemented\r\n$"); +#else + printf("This program has attempted to call USERF, which " + "is not implemented.\n"); +#endif + zx_term(); + zx_exit(1); + break; + + default: +#ifdef USE_CPMIO + cpm_scr_unit(); +#endif +#ifdef USE_CPMGSX + gsx_deinit(); +#endif + + fprintf(stderr,"%s: Unsupported BIOS call %d\n", progname, func); + dump_regs(stderr,*a,*b,*c,*d,*e,*f,*h,*l,*pc,*ix,*iy); + zx_exit(1); + } + + Msg("BIOS service completion.\n"); +} diff --git a/Tools/unix/zx/zxbdos.h b/Tools/unix/zx/zxbdos.h new file mode 100644 index 00000000..4111c78e --- /dev/null +++ b/Tools/unix/zx/zxbdos.h @@ -0,0 +1,50 @@ +extern char *progname; +extern char **argv; +extern int argc; + +extern byte cpm_drive; +extern byte cpm_user; + +extern byte RAM[65536]; /* The Z80's address space */ + +extern void Msg(char *s, ...); + +#ifdef BDOS_DEF + +word cpm_dma = 0x80; /* DMA address */ +byte err_mode = 0xFF; +byte rec_multi = 1; +word rec_len = 128; +word ffirst_fcb = 0xFFFF; +byte cpm_error = 0; /* Error code returned by CP/M */ + +#else /* BDOS_DEF */ + +extern word cpm_dma, rec_len, ffirst_fcb; +extern byte err_mode, rec_multi, cpm_error; + +#endif /* BDOS_DEF */ + +#ifndef O_BINARY /* Necessary in DOS, not present in Linux */ +#define O_BINARY 0 +#endif + +typedef unsigned long dword; + +/* Functions in zxbdos.c */ + +void wr24(word addr, dword v); +void wr32(word addr, dword v); +dword rd24(word addr); +dword rd32(word addr); +dword cpmtime(time_t t); +word cpm_errcde(word DE); + +#ifdef USE_CPMGSX +gsx_byte gsxrd(gsx_word addr); +void gsxwr(gsx_word addr, gsx_byte value); +#endif + +void cpmbdos(); +void cpmbios(); + diff --git a/Tools/unix/zx/zxcbdos.c b/Tools/unix/zx/zxcbdos.c new file mode 100644 index 00000000..b441fa7f --- /dev/null +++ b/Tools/unix/zx/zxcbdos.c @@ -0,0 +1,114 @@ +#include "zx.h" +#include "zxbdos.h" +#include "zxcbdos.h" +#include +#ifdef WIN32 +#include +#endif + +/* Line input */ +#ifdef USE_CPMIO + + +void bdos_rdline(word line, word *PC) +{ + char *buf; + + if (!line) line = cpm_dma; + else RAM[line + 1] = 0; + + buf = (char *)&RAM[line]; + + if (cpm_bdos_10(buf)) *PC = 0; +} + +#else /* def USE_CPMIO */ + +void bdos_rdline(word line, word *PC) +{ + int maxlen; + + if (!line) line = cpm_dma; + maxlen = RAM[line]; + + fgets((char *)(RAM + line + 2), maxlen, stdin); + RAM[line + 1] = strlen((char *)(RAM + line + 2)) - 1; + + Msg("Input: [%d] %-*.*s\n", RAM[line + 1], RAM[line + 1], RAM[line +1], (char *)(RAM+line+2)); +} +#endif /* ndef USE_CPMIO */ + +#ifndef USE_CPMIO + + +int cpm_bdos_6(byte e) +{ + int c; + + switch(e) { + case 0xFF: + if (cstat()) return cin(); + return 0; + + case 0xFE: + return cstat(); + + case 0xFD: + return cin(); + + default: + cout(e); + break; + } + return 0; +} +#endif + +#if defined(__MINGW32__) || defined(_MSC_BUILD) || defined(__WATCOMC__) + +byte cin() +{ + if (_isatty(_fileno(stdin))) + return getch(); + else + return getchar(); +} + +void cout(byte c) +{ + if (_isatty(_fileno(stdout))) + putch(c); + else + putchar(c); +} + +int cstat() +{ + if (_isatty(_fileno(stdin))) + return _kbhit() ? 0xFF : 0; + else + return 0xFF; +} + +#else /* defined(__MINGW32__) || defined(_MSC_BUILD) */ + +byte cin() +{ + return getchar(); +} + +void cout(byte c) +{ + putchar(c); +} + +int cstat() +{ + int i; + + ioctl(_fileno(stdin), FIONREAD, &i); + if (i > 0) return 0xff; + return 0; +} + +#endif diff --git a/Tools/unix/zx/zxcbdos.h b/Tools/unix/zx/zxcbdos.h new file mode 100644 index 00000000..7f724441 --- /dev/null +++ b/Tools/unix/zx/zxcbdos.h @@ -0,0 +1,4 @@ +void bdos_rdline(word line, word *PC); +int cpm_bdos_6(byte e); + + diff --git a/Tools/unix/zx/zxdbdos.c b/Tools/unix/zx/zxdbdos.c new file mode 100644 index 00000000..f5f21f3b --- /dev/null +++ b/Tools/unix/zx/zxdbdos.c @@ -0,0 +1,88 @@ +#include "zx.h" +#include "zxbdos.h" +#include "zxdbdos.h" + +/* This file used to deal with all disc-based BDOS calls. + Now the calls have been moved into libcpmredir, it's a bit empty round + here. + + ZXCC does a few odd things when searching, to make Hi-Tech C behave + properly. +*/ + + +/* If a file could not be found on the default drive, try again on a "search" + drive (A: for .COM files, B: for .LIB and .OBJ files) */ + +int fcbforce(byte *fcb, byte *odrv) +{ + byte drive; + char typ[4]; + int n; + + for (n = 0; n < 3; n++) typ[n] = fcb[n+9] & 0x7F; + typ[3] = 0; + + Msg("fcbforce: typ=%s, fcb=%hhx\r\n", typ, *fcb); + + drive = 0; + if (*fcb) return 0; /* not using default drive */ + //if ((*fcb) != 16) return 0; /* not using default drive */ + if (!strcmpi(typ, "COM")) drive = 1; + if (!strcmpi(typ, "LIB")) drive = 2; + if (!strcmpi(typ, "OBJ")) drive = 2; + if (!strcmpi(typ, "H ")) drive = 3; + + Msg("fcbforce: drive=%i\r\n", drive); + + if (!drive) return 0; + + *odrv = *fcb; + *fcb = drive; + return 1; +} + +/* zxcc has a trick with some filenames: If it can't find them where they + should be, and a drive wasn't specified, it searches BINDIR80, + LIBDIR80 or INCDIR80 (depending on the type of the file). + */ + +word x_fcb_open(byte *fcb, byte *dma) +{ + word rv = fcb_open(fcb, dma); + byte odrv; + + Msg("x_fcb_open: rv=%X\r\n", rv); + + if (rv == 0xFF) + { + if (fcbforce(fcb, &odrv)) + { + rv = fcb_open(fcb, dma); + Msg("x_fcb_open: rv=%X\r\n", rv); + *fcb = odrv; + } + } + return rv; +} + + + +word x_fcb_stat(byte *fcb) +{ + word rv = fcb_stat(fcb); + byte odrv; + + if (rv == 0xFF) + { + if (fcbforce(fcb, &odrv)) + { + rv = fcb_stat(fcb); + *fcb = odrv; + } + } + return rv; +} + + + diff --git a/Tools/unix/zx/zxdbdos.h b/Tools/unix/zx/zxdbdos.h new file mode 100644 index 00000000..7a47b801 --- /dev/null +++ b/Tools/unix/zx/zxdbdos.h @@ -0,0 +1,8 @@ + + + +int fcbforce(byte *fcb, byte *odrv); + +word x_fcb_open(byte *fcb, byte *dma); +word x_fcb_stat(byte *fcb); +