From: Philip Chimento Date: Sun, 16 Sep 2012 17:52:07 +0000 (+0200) Subject: Add Bocfel interpreter X-Git-Tag: v0.9~2 X-Git-Url: https://git.stderr.nl/gitweb?p=projects%2Fchimara%2Fchimara.git;a=commitdiff_plain;h=aa30979369091c96bca34499c28cb01bc16efb1d Add Bocfel interpreter The Bocfel interpreter is now available for playing Z-machine files. --- diff --git a/configure.ac b/configure.ac index 391b91f..54b947f 100644 --- a/configure.ac +++ b/configure.ac @@ -169,6 +169,7 @@ interpreters/frotz/Makefile interpreters/nitfol/Makefile interpreters/glulxe/Makefile interpreters/git/Makefile +interpreters/bocfel/Makefile tests/Makefile player/Makefile player/config.py diff --git a/interpreters/Makefile.am b/interpreters/Makefile.am index 0b1cb2e..cd8e930 100644 --- a/interpreters/Makefile.am +++ b/interpreters/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS=nitfol frotz glulxe git +SUBDIRS=nitfol frotz glulxe git bocfel -include $(top_srcdir)/git.mk diff --git a/interpreters/bocfel/BUILDING b/interpreters/bocfel/BUILDING new file mode 100644 index 0000000..30411e8 --- /dev/null +++ b/interpreters/bocfel/BUILDING @@ -0,0 +1,135 @@ +GNU make is required, and all of the examples herein assume that GNU make is +being used. If your native make is not GNU, substitute gmake for make. + +A C99 compiler is required to build Bocfel; or at least a compiler that supports +the C99 features that are used. These include, but are probably not limited to, +VLAs, snprintf(), mixed declarations and code, fixed-width integers, and +compound literals. + +Officially supported compilers are handled in compiler.mk. If you get Bocfel +working with another compiler, I would be happy to add it to compiler.mk. Note +that Bocfel has a few requirements (beyond C99) from its compilers. Compilers +are expected to support the following rather standard Unix command-line +arguments, and are expected to work as linkers, as well: +-D - define a macro +-L - define a library search path +-l - specify a library to link +-o - specify the output file name +-g - build with debugging symbols +-c - compile but don't link, producing a .o file from a .c file +-O - Optimize (but this can be changed via the $OPT variable) + +Compilers are also expected to understand optimization flags while they are +linking; modern compilers can do link-time optimization, and generally require +the same flags when compiling and linking (the system linker is usually not +smart enough to handle the link-time optimized object files). + +The character set used is required to be compatible with ASCII. This is not a +particularly onerous requirement; but if I ever have access to a system that +supports a wildly different character set, I would be willing to think about +extending support beyond ASCII. For now, though, it is much easier to assume +ASCII, because almost everybody uses a character set that is compatible with it, +including the Z-machine. + +A 32-bit (or greater) system is probably a requirement. I have tried to avoid +the assumption that "int" is 32 bits, and it is entirely possible that I have +succeeded and a system with 16-bit ints would work properly. However, there are +some lookup tables that are 65K each, which would probably cause fits for 16-bit +systems. As with the ASCII requirement, if I ever happen upon a 16-bit system +with a C99 compiler, I will probably try to get Bocfel working with it. + +I make use of fixed-width types such as uint8_t and uint16_t. As a practical +side-effect, Bocfel requires a system with 8-, 16-, and 32-bit 2's complement +integers. This is likely not a problem. + +------------------------------------------------------------------------------- + +There are two main types of build: Glk and non-Glk. Glk builds use libraries +based on Andrew Plotkin's Glk standard for I/O. Glk builds can be full- +featured, including timed input and cursor control, allowing games like Border +Zone and Seastalker to run as intended. Non-Glk builds use nothing but standard +C functions. This has the advantage of running on systems where Glk has not +been ported, but the disadvantage of missing important features. Non-Glk builds +are generally not useful. + +The first thing to do is edit config.mk and set $PLATFORM to the proper value. +See the comments in that file for an explanation. There are other variables +that may be set through config.mk, each of which has comments explaining its +use. + +To build a non-Glk interpreter, simply run: +make GLK= + +The $GLK variable may also be set through config.mk. + +Glk builds are slightly more involved. For most Glk libraries (e.g. glkterm(w), +xglk, cheapglk, Windows Glk), you will want to unpack the source distribution +into the current directory (the one containing Bocfel's source), and then build +it. After this is done, simply run +make GLK=glktermw + +to build a Glk-enabled interpreter (where glktermw is the name of the directory +into which the Glk library was unpacked). Note that the Windows Glk +distribution does not unpack into its own directory, so you will want to change +into the winglk directory before unpacking it. + +The presence of a file called Make. in the Glk library +directory is required. Most Glk libraries will include this, but at least +Windows Glk does not. I have included a Make.winglk that should be sufficient +to build a Windows Glk-enabled interpreter, at least with MinGW. + +Bocfel can also be built against Gargoyle's Glk implementation, taking full +advantage of extra features it provides. Ben Cressey, Gargoyle's maintainer, +has imported Bocfel into the Gargoyle source repository, so the easiest way to +obtain Gargoyle support is to check out the latest version of Gargoyle's source +code; see http://code.google.com/p/garglk/source/checkout. If you would prefer +to use Bocfel's build system to build against Gargoyle as you would any other +Glk library, read on. + +The build process for Gargoyle is slightly more involved. Gargoyle includes a +full-featured Glk library, but it is not designed for external use. However, +getting a Gargoyle- (or rather, a garglk-) enabled build is not too difficult. + +The first step is to get and install the Jam build tool from +http://www.perforce.com/jam/jam.html. Your vendor may provide a jam package or +port; consult its documentation. + +Next, get a copy of the Gargoyle source code from http://garglk.googlecode.com/. +Extract it somewhere (it need not be in the current directory). Enter the +directory and run jam. From the build/.release/garglk directory, copy +the files libgarglk.so and libgarglkmain.a to the directory "gargoyle" (which +contains a Make.gargoyle file) in the Bocfel source distribution. Then copy the +files garglk/glk.h, garglk/glkstart.h, and garglk/gi_blorb.h from Gargoyle to +Bocfel's gargoyle directory. You can now build a garglk-enabled interpreter: +make GLK=gargoyle + +If you do not already have Gargoyle installed, you will need to install the +library libgarglk.so somewhere in your library search path. I would, however, +recommend just installing Gargoyle. If you do this, you will want to build the +source code of the same version that you install. If you don't, it's possible +(although unlikely) that ABI or API changes in Gargoyle will cause problems. + +Bocfel is built and tested against the development version of Gargoyle, so +ideally this would be the version to build against. However, Gargoyle does, by +and large, implement a standard API, so the version should not make a large +difference. There are a few Gargoyle-specific extensions that can possibly +change, but they have been stable apart from one change over the past couple of +years. The current (2010.1) release of Gargoyle should work, but the only +officially supported version is whatever was current as of this version of +Bocfel's release. + +------------------------------------------------------------------------------- + +Glk is very portable, and there is very little system-specific code that is +needed. However, there is a small amount. Some libraries are, conventionally, +considered to be Unix (cheapglk, xglk, and glkterm(w)); one Windows (Windows +Glk, naturally). Bocfel needs to know what kind of platform it is running on in +order to provide a few system-specific functions, and it uses this information +to decide what kind of Glk library is in use. While it is theoretically +possible (for example) for a Unix-based Glk library to use a non Unix-style Glk +startup, I have no desire to require users to set both their OS and Glk +platforms. Thus a Windows user who desires to use cheapglk (assuming it builds +on Windows) is out of luck, for example. I consider this to be acceptable +because I expect garglk to be the preferred Glk implementation, and even if it's +not, most Glk uses will work. When building with garglk, Unix-style Glk startup +is forced, because this is what garglk uses, regardless of platform. diff --git a/interpreters/bocfel/COPYING.GPLv2 b/interpreters/bocfel/COPYING.GPLv2 new file mode 100644 index 0000000..a3f6b12 --- /dev/null +++ b/interpreters/bocfel/COPYING.GPLv2 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + END OF TERMS AND CONDITIONS + + Appendix: 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 +convey 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) 19yy + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/interpreters/bocfel/COPYING.GPLv3 b/interpreters/bocfel/COPYING.GPLv3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/interpreters/bocfel/COPYING.GPLv3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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/interpreters/bocfel/Makefile.am b/interpreters/bocfel/Makefile.am new file mode 100644 index 0000000..ecb779e --- /dev/null +++ b/interpreters/bocfel/Makefile.am @@ -0,0 +1,15 @@ +pkglib_LTLIBRARIES = bocfel.la +bocfel_la_SOURCES = blorb.c blorb.h branch.c branch.h dict.c dict.h glkstart.c \ + iff.c iff.h io.c io.h math.c math.h memory.c memory.h objects.c objects.h \ + osdep.c osdep.h process.c process.h random.c random.h screen.c screen.h \ + stack.c stack.h table.c table.h unicode.c unicode.h util.c util.h zoom.c \ + zoom.h zterp.c zterp.h +bocfel_la_CPPFLAGS = -DZTERP_GLK -DZTERP_UNIX \ + -I$(top_srcdir) -I$(top_srcdir)/libchimara +bocfel_la_CFLAGS = -std=c99 $(AM_CFLAGS) +bocfel_la_LDFLAGS = -module $(PLUGIN_LIBTOOL_FLAGS) + +bocfeldocdir = $(datadir)/doc/$(PACKAGE)/bocfel +dist_bocfeldoc_DATA = BUILDING COPYING.GPLv2 COPYING.GPLv3 README + +-include $(top_srcdir)/git.mk diff --git a/interpreters/bocfel/README b/interpreters/bocfel/README new file mode 100644 index 0000000..1d22cd3 --- /dev/null +++ b/interpreters/bocfel/README @@ -0,0 +1,18 @@ +Bocfel is an interpreter for the Z-machine, which means that it plays text +adventure games. For more information on the Z-machine and interpreters, please +see http://en.wikipedia.org/wiki/Z-machine. + +For building instructions, please consult the BUILDING file. + +If you are poking around the source code, you might notice a lot of identifiers +containing "zterp". When I first started this project, I put it in my +Subversion repository, but I wasn't sure it would ever go anywhere. I quickly +picked a rather generic name--zterp--because a name was needed for the +repository path. It turns out, however, that long ago somebody released a +Z-machine interpreter called zterp, so I had to find something new. By the time +I realized this, however, the name "zterp" had become entrenched in the source. +I've changed all user-visible uses of "zterp" to "bocfel", but have kept the old +identifiers in the source. + +Chris Spiegel +http://bocfel.googlecode.com/ diff --git a/interpreters/bocfel/blorb.c b/interpreters/bocfel/blorb.c new file mode 100644 index 0000000..456e976 --- /dev/null +++ b/interpreters/bocfel/blorb.c @@ -0,0 +1,121 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include + +#include "blorb.h" +#include "iff.h" +#include "io.h" + +struct zterp_blorb +{ + struct zterp_io *io; + struct zterp_iff *iff; + + size_t nchunks; + zterp_blorb_chunk *chunks; +}; + +struct zterp_blorb *zterp_blorb_parse(zterp_io *io) +{ + uint32_t size; + uint32_t nresources; + zterp_iff *iff; + struct zterp_blorb *blorb = NULL; + + iff = zterp_iff_parse(io, "IFRS"); + if(!zterp_iff_find(iff, "RIdx", &size)) goto err; + zterp_iff_free(iff); + + if(!zterp_io_read32(io, &nresources)) goto err; + + if((nresources * 12) + 4 != size) goto err; + + blorb = malloc(sizeof *blorb); + if(blorb == NULL) goto err; + + blorb->io = io; + blorb->nchunks = 0; + blorb->chunks = NULL; + + for(uint32_t i = 0; i < nresources; i++) + { + uint32_t usage, number, start, type; + zterp_blorb_chunk *new; + long saved; + uint32_t idx; + + if(!zterp_io_read32(io, &usage) || !zterp_io_read32(io, &number) || !zterp_io_read32(io, &start)) goto err; + + if(usage != BLORB_PICT && usage != BLORB_SND && usage != BLORB_EXEC) goto err; + + saved = zterp_io_tell(io); + if(saved == -1) goto err; + + if(zterp_io_seek(io, start, SEEK_SET) == -1) goto err; + + if(!zterp_io_read32(io, &type) || !zterp_io_read32(io, &size)) goto err; + + if(zterp_io_seek(io, saved, SEEK_SET) == -1) goto err; + + if(type == STRID("FORM")) + { + start -= 8; + size += 8; + } + + /* Not really efficient, but does it matter? */ + new = realloc(blorb->chunks, sizeof *new * ++blorb->nchunks); + if(new == NULL) goto err; + blorb->chunks = new; + + idx = blorb->nchunks - 1; + + new[idx].usage = usage; + new[idx].number = number; + new[idx].type = type; + memcpy(new[idx].name, IDSTR(type), 5); + new[idx].offset = start + 8; + new[idx].size = size; + } + + return blorb; + +err: + zterp_blorb_free(blorb); + + return NULL; +} + +void zterp_blorb_free(struct zterp_blorb *blorb) +{ + if(blorb != NULL) free(blorb->chunks); + free(blorb); +} + +const zterp_blorb_chunk *zterp_blorb_find(struct zterp_blorb *blorb, uint32_t usage, int number) +{ + for(size_t i = 0; i < blorb->nchunks; i++) + { + if(blorb->chunks[i].usage == usage && blorb->chunks[i].number == number) return &blorb->chunks[i]; + } + + return NULL; +} diff --git a/interpreters/bocfel/blorb.h b/interpreters/bocfel/blorb.h new file mode 100644 index 0000000..654becc --- /dev/null +++ b/interpreters/bocfel/blorb.h @@ -0,0 +1,29 @@ +#ifndef ZTERP_BLORB_H +#define ZTERP_BLORB_H + +#include +#include + +#include "io.h" + +#define BLORB_PICT 0x50696374 +#define BLORB_SND 0x536e6420 +#define BLORB_EXEC 0x45786563 + +typedef struct zterp_blorb zterp_blorb; + +typedef struct +{ + uint32_t usage; + int number; + uint32_t type; + char name[5]; + uint32_t offset; + uint32_t size; +} zterp_blorb_chunk; + +zterp_blorb *zterp_blorb_parse(zterp_io *); +void zterp_blorb_free(zterp_blorb *); +const zterp_blorb_chunk *zterp_blorb_find(zterp_blorb *, uint32_t, int); + +#endif diff --git a/interpreters/bocfel/branch.c b/interpreters/bocfel/branch.c new file mode 100644 index 0000000..2af3d29 --- /dev/null +++ b/interpreters/bocfel/branch.c @@ -0,0 +1,91 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include + +#include "branch.h" +#include "memory.h" +#include "process.h" +#include "stack.h" +#include "util.h" +#include "zterp.h" + +void branch_if(int do_branch) +{ + uint8_t branch; + uint16_t offset; + + branch = BYTE(pc++); + + if(!do_branch) branch ^= 0x80; + + offset = branch & 0x3f; + + if((branch & 0x40) == 0) + { + offset = (offset << 8) | BYTE(pc++); + + /* Get the sign right. */ + if(offset & 0x2000) offset |= 0xc000; + } + + if(branch & 0x80) + { + if(offset > 1) + { + pc += (int16_t)offset - 2; + ZASSERT(pc < memory_size, "branch to invalid address 0x%lx", (unsigned long)pc); + } + else + { + do_return(offset); + } + } +} + +void zjump(void) +{ + /* -= 2 because pc has been advanced past the jump instruction. */ + pc += (int16_t)zargs[0]; + pc -= 2; + + ZASSERT(pc < memory_size, "@jump to invalid address 0x%lx", (unsigned long)pc); +} + +void zjz(void) +{ + branch_if(zargs[0] == 0); +} + +void zje(void) +{ + if (znargs == 1) branch_if(0); + else if(znargs == 2) branch_if(zargs[0] == zargs[1]); + else if(znargs == 3) branch_if(zargs[0] == zargs[1] || zargs[0] == zargs[2]); + else branch_if(zargs[0] == zargs[1] || zargs[0] == zargs[2] || zargs[0] == zargs[3]); +} + +void zjl(void) +{ + branch_if((int16_t)zargs[0] < (int16_t)zargs[1]); +} + +void zjg(void) +{ + branch_if((int16_t)zargs[0] > (int16_t)zargs[1]); +} diff --git a/interpreters/bocfel/branch.h b/interpreters/bocfel/branch.h new file mode 100644 index 0000000..709860c --- /dev/null +++ b/interpreters/bocfel/branch.h @@ -0,0 +1,12 @@ +#ifndef ZTERP_BRANCH_H +#define ZTERP_BRANCH_H + +void branch_if(int); + +void zjump(void); +void zjz(void); +void zje(void); +void zjl(void); +void zjg(void); + +#endif diff --git a/interpreters/bocfel/dict.c b/interpreters/bocfel/dict.c new file mode 100644 index 0000000..f184ddb --- /dev/null +++ b/interpreters/bocfel/dict.c @@ -0,0 +1,319 @@ +/*- + * Copyright 2009-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#include "dict.h" +#include "memory.h" +#include "process.h" +#include "unicode.h" +#include "util.h" +#include "zterp.h" + +static uint16_t separators; +static uint8_t num_separators; + +static uint16_t GET_WORD(uint8_t *base) +{ + return (base[0] << 8) | base[1]; +} +static void MAKE_WORD(uint8_t *base, uint16_t val) +{ + base[0] = val >> 8; + base[1] = val & 0xff; +} + +/* Add the character c to the nth position of the encoded text. c is a + * 5-bit value (either a shift character, which selects an alphabet, or + * the index into the current alphabet). + */ +static void add_zchar(int c, int n, uint8_t *encoded) +{ + uint16_t w = GET_WORD(&encoded[2 * (n / 3)]); + + /* From §3.2: + * --first byte------- --second byte--- + * 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * bit --first-- --second--- --third-- + * + * So to figure out which third of the word to store to: + * If n is 0, 3, 6, ... then store to the first (left shift 10). + * If n is 1, 4, 7, ... then store to the second (left shift 5). + * If n is 2, 5, 8, ... then store to the third (left shift 0). + * “Or” into the previous value because, unless this is the first + * character, there are already values we’ve stored there. + */ + w |= (c & 0x1f) << (5 * (2 - (n % 3))); + + MAKE_WORD(&encoded[2 * (n / 3)], w); +} + +/* Encode the text at “s”, of length “len” (there is not necessarily a + * terminating null character) into the buffer “encoded”. + * + * For V3 the encoded text is 6 Z-characters (4 bytes); for V4 and above + * it’s 9 characters (6 bytes). Due to the nature of the loop here, + * it’s possible to encode too many bytes. For example, if the string + * given is "aaa<" in a V3 game, the three 'a' characters will take up a + * word (all three being packed into one), but the single '<' character + * will take up two words (one full word and a third of the next) due to + * the fact that '<' is not in the alphabet table. Thus the encoded + * text will be 7 characters. This is OK because routines that use the + * encoded string are smart enough to only pay attention to the first 6 + * or 9 Z-characters; and partial Z-characters are OK per §3.6.1. + * + * 1.1 of the standard revises the encoding for V1 and V2 games. I am + * not implementing the new rules for two basic reasons: + * 1) It apparently only affects three (unnecessary) dictionary words in + * the known V1-2 games. + * 2) Because of 1, it is not worth the effort to peek ahead and see + * what the next character is to determine whether to shift once or + * to lock. + * + * Z-character 0 is a space (§3.5.1), so theoretically a space should be + * encoded simply with a zero. However, Inform 6.32 encodes space + * (which has the value 32) as a 10-bit ZSCII code, which is the + * Z-characters 5, 6, 1, 0. Assume this is correct. + */ +static void encode_string(const uint8_t *s, size_t len, uint8_t encoded[8]) +{ + int n = 0; + const int res = zversion <= 3 ? 6 : 9; + const int shiftbase = zversion <= 2 ? 1 : 3; + + memset(encoded, 0, 8); + + for(size_t i = 0; i < len && n < res; i++) + { + int pos; + + pos = atable_pos[s[i]]; + if(pos >= 0) + { + int shift = pos / 26; + int c = pos % 26; + + if(shift) add_zchar(shiftbase + shift, n++, encoded); + add_zchar(c + 6, n++, encoded); + } + else + { + add_zchar(shiftbase + 2, n++, encoded); + add_zchar(6, n++, encoded); + add_zchar(s[i] >> 5, n++, encoded); + add_zchar(s[i] & 0x1f, n++, encoded); + } + } + + while(n < res) + { + add_zchar(5, n++, encoded); + } + + /* §3.2: the MSB of the last encoded word must be set. */ + if(zversion <= 3) encoded[2] |= 0x80; + else encoded[4] |= 0x80; +} + +static int dict_compar(const void *a, const void *b) +{ + return memcmp(a, b, zversion <= 3 ? 4 : 6); +} +static uint16_t dict_find(const uint8_t *token, size_t len, uint16_t dictionary) +{ + uint8_t elength; + uint16_t base; + long nentries; + uint8_t *ret = NULL; + uint8_t encoded[8]; + + encode_string(token, len, encoded); + + elength = user_byte(dictionary + num_separators + 1); + nentries = (int16_t)user_word(dictionary + num_separators + 2); + base = dictionary + num_separators + 2 + 2; + + ZASSERT(elength >= (zversion <= 3 ? 4 : 6), "dictionary entry length (%d) too small", elength); + ZASSERT(base + (labs(nentries) * elength) < memory_size, "reported dictionary length extends beyond memory size"); + + if(nentries > 0) + { + ret = bsearch(encoded, &memory[base], nentries, elength, dict_compar); + } + else + { + for(long i = 0; i < -nentries; i++) + { + uint8_t *entry = &memory[base + (i * elength)]; + + if(dict_compar(encoded, entry) == 0) + { + ret = entry; + break; + } + } + } + + if(ret == NULL) return 0; + + return base + (ret - &memory[base]); +} + +static int is_sep(uint8_t c) +{ + if(c == ZSCII_SPACE) return 1; + + for(uint16_t i = 0; i < num_separators; i++) if(user_byte(separators + i) == c) return 1; + + return 0; +} + +static void handle_token(const uint8_t *base, const uint8_t *token, int len, uint16_t parse, uint16_t dictionary, int found, int flag) +{ + uint16_t d; + const uint8_t examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e' }; + const uint8_t again[] = { 'a', 'g', 'a', 'i', 'n' }; + const uint8_t wait[] = { 'w', 'a', 'i', 't' }; + + d = dict_find(token, len, dictionary); + + if(!options.disable_abbreviations && base == token && len == 1) + { + if (*token == 'x') d = dict_find(examine, sizeof examine, dictionary); + else if(*token == 'g') d = dict_find(again, sizeof again, dictionary); + else if(*token == 'z') d = dict_find(wait, sizeof wait, dictionary); + } + + if(flag && d == 0) return; + + parse = parse + 2 + (found * 4); + + user_store_word(parse, d); + + user_store_byte(parse + 2, len); + + if(zversion <= 4) user_store_byte(parse + 3, token - base + 1); + else user_store_byte(parse + 3, token - base + 2); +} + +/* The behavior of tokenize is described in §15 (under the read opcode) + * and §13. + * + * For the text buffer, byte 0 is ignored in both V3/4 and V5+. + * Byte 1 of V3/4 is the start of the string, while in V5+ it is the + * length of the string. + * Byte 2 of V5+ is the start of the string. V3/4 strings have a null + * terminator, while V5+ do not. + * + * For the parse buffer, byte 0 contains the maximum number of tokens + * that can be read. + * The number of tokens found is stored in byte 1. + * Each token is then represented by a 4-byte chunk with the following + * information: + * • The first two bytes are the byte address of the dictionary entry + * for the token, or 0 if the token was not found in the dictionary. + * • The next byte is the length of the token. + * • The final byte is the offset in the string of the token. + */ +void tokenize(uint16_t text, uint16_t parse, uint16_t dictionary, int flag) +{ + const uint8_t *p, *lastp; + uint8_t *string; + uint32_t text_len = 0; + const int maxwords = user_byte(parse); + int in_word = 0; + int found = 0; + + if(dictionary == 0) dictionary = header.dictionary; + + ZASSERT(dictionary != 0, "attempt to tokenize without a valid dictionary"); + + num_separators = user_byte(dictionary); + separators = dictionary + 1; + + if(zversion >= 5) text_len = user_byte(text + 1); + else while(user_byte(text + 1 + text_len) != 0) text_len++; + + ZASSERT(text + 1 + (zversion >= 5) + text_len < memory_size, "attempt to tokenize out-of-bounds string"); + + string = &memory[text + 1 + (zversion >= 5)]; + + for(p = string; p - string < text_len && *p == ZSCII_SPACE; p++); + lastp = p; + + text_len -= (p - string); + + do + { + if(!in_word && text_len != 0 && !is_sep(*p)) + { + in_word = 1; + lastp = p; + } + + if(text_len == 0 || is_sep(*p)) + { + if(in_word) + { + handle_token(string, lastp, p - lastp, parse, dictionary, found++, flag); + } + + /* §13.6.1: Separators (apart from a space) are tokens too. */ + if(text_len != 0 && *p != ZSCII_SPACE) + { + handle_token(string, p, 1, parse, dictionary, found++, flag); + } + + if(found == maxwords) break; + + in_word = 0; + } + + p++; + + } while(text_len--); + + user_store_byte(parse + 1, found); +} + +static void encode_text(uint32_t text, uint16_t len, uint16_t coded) +{ + uint8_t encoded[8]; + + ZASSERT(text + len < memory_size, "reported text length extends beyond memory size"); + + encode_string(&memory[text], len, encoded); + + for(int i = 0; i < 6; i++) user_store_byte(coded + i, encoded[i]); +} + +void ztokenise(void) +{ + if(znargs < 3) zargs[2] = 0; + if(znargs < 4) zargs[3] = 0; + + tokenize(zargs[0], zargs[1], zargs[2], zargs[3]); +} + +void zencode_text(void) +{ + encode_text(zargs[0] + zargs[2], zargs[1], zargs[3]); +} diff --git a/interpreters/bocfel/dict.h b/interpreters/bocfel/dict.h new file mode 100644 index 0000000..e1c8c02 --- /dev/null +++ b/interpreters/bocfel/dict.h @@ -0,0 +1,11 @@ +#ifndef DICTIONARY_H +#define DICTIONARY_H + +#include + +void tokenize(uint16_t, uint16_t, uint16_t, int); + +void ztokenise(void); +void zencode_text(void); + +#endif diff --git a/interpreters/bocfel/glkstart.c b/interpreters/bocfel/glkstart.c new file mode 100644 index 0000000..89cfb27 --- /dev/null +++ b/interpreters/bocfel/glkstart.c @@ -0,0 +1,120 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +/* Even on Win32, Gargoyle provides a glkunix startup. */ +#if defined(ZTERP_UNIX) || defined(GARGLK) +#include + +#include + +#include "util.h" +#include "zterp.h" + +zexternally_visible +glkunix_argumentlist_t glkunix_arguments[] = +{ + { "-a", glkunix_arg_NumberValue, "-a N set the size of the evaluation stack" }, + { "-A", glkunix_arg_NumberValue, "-A N set the size of the call stack" }, + { "-c", glkunix_arg_NoValue, "-c disable color" }, + { "-C", glkunix_arg_NoValue, "-C disable the use of a config file" }, + { "-d", glkunix_arg_NoValue, "-d disable timed input" }, + { "-D", glkunix_arg_NoValue, "-D disable sound effects" }, + { "-e", glkunix_arg_NoValue, "-e enable ANSI escapes in the transcript" }, + { "-E", glkunix_arg_ValueFollows, "-E string set the escape string for -e" }, + { "-f", glkunix_arg_NoValue, "-f disable fixed-width fonts" }, + { "-F", glkunix_arg_NoValue, "-F assume font is fixed-width" }, + { "-g", glkunix_arg_NoValue, "-g disable the character graphics font" }, + { "-G", glkunix_arg_NoValue, "-G enable alternative box-drawing character graphics" }, + { "-i", glkunix_arg_NoValue, "-i display the id of the story file and exit" }, + { "-k", glkunix_arg_NoValue, "-k disable the use of terminating keys (notably used in Beyond Zork)" }, + { "-l", glkunix_arg_NoValue, "-l disable utf-8 transcripts" }, + { "-L", glkunix_arg_NoValue, "-L force utf-8 transcrips" }, + { "-m", glkunix_arg_NoValue, "-m disable meta commands" }, + { "-n", glkunix_arg_NumberValue, "-n N set the interpreter number (see 11.1.3 in The Z-machine Standards Document 1.0)" }, + { "-N", glkunix_arg_NumberValue, "-N N set the interpreter version (see 11.1.3.1 in The Z-machine Standards Document 1.0)" }, + { "-r", glkunix_arg_NoValue, "-r start the story by replaying a command record" }, + { "-R", glkunix_arg_NoValue, "-R filename set the filename to be used if replaying a command record" }, + { "-s", glkunix_arg_NoValue, "-s start the story with command recording on" }, + { "-S", glkunix_arg_NoValue, "-S filename set the filename to be used if command recording is turned on" }, + { "-t", glkunix_arg_NoValue, "-t start the story with transcripting on" }, + { "-T", glkunix_arg_ValueFollows, "-T filename set the filename to be used if transcription is turned on" }, + { "-u", glkunix_arg_NumberValue, "-u N set the maximum number of undo slots" }, + { "-U", glkunix_arg_NoValue, "-U disable compression in undo slots" }, + { "-v", glkunix_arg_NoValue, "-v display version information" }, + { "-x", glkunix_arg_NoValue, "-x disable expansion of abbreviations" }, + { "-X", glkunix_arg_NoValue, "-X enable tandy censorship" }, + { "-y", glkunix_arg_NoValue, "-y when opening a transcript, overwrite rather than append to an existing file" }, + { "-z", glkunix_arg_NumberValue, "-z N set initial random seed" }, + { "-Z", glkunix_arg_ValueFollows, "-Z device read initial random seed from device" }, + { "", glkunix_arg_ValueFollows, "filename file to load" }, + + { NULL, glkunix_arg_End, NULL } +}; + +zexternally_visible +int glkunix_startup_code(glkunix_startup_t *data) +{ + if(!process_arguments(data->argc, data->argv)) return 0; + +#ifdef GARGLK + garglk_set_program_name("Bocfel"); + if(game_file != NULL) + { + char *p = strrchr(game_file, '/'); + garglk_set_story_name(p == NULL ? game_file : p + 1); + } +#endif + + return 1; +} +#elif defined(ZTERP_WIN32) +#include + +#include + +#include "util.h" + +int InitGlk(unsigned int); + +zexternally_visible +int WINAPI WinMain(HINSTANCE instance, HINSTANCE previnstance, LPSTR cmdline, int cmdshow) +{ + /* This works (with a linker message) under MinGW, but I don’t + * know if it’s supposed to; I am unfamiliar with how Windows + * handles command-line arguments. + */ + extern int __argc; + extern char **__argv; + + if(!InitGlk(0x00000700)) exit(EXIT_FAILURE); + + if(!process_arguments(__argc, __argv)) exit(EXIT_FAILURE); + + winglk_app_set_name("Bocfel"); + + glk_main(); + glk_exit(); + + return 0; +} +#else +#error Glk on this platform is not supported. +#endif diff --git a/interpreters/bocfel/iff.c b/interpreters/bocfel/iff.c new file mode 100644 index 0000000..e7fe4e1 --- /dev/null +++ b/interpreters/bocfel/iff.c @@ -0,0 +1,112 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include + +#include "iff.h" +#include "io.h" + +struct zterp_iff +{ + zterp_io *io; + uint32_t tag; + long offset; + uint32_t size; + + struct zterp_iff *next; +}; + +void zterp_iff_free(zterp_iff *iff) +{ + while(iff != NULL) + { + zterp_iff *tmp = iff->next; + free(iff); + iff = tmp; + } +} + +zterp_iff *zterp_iff_parse(zterp_io *io, const char type[4]) +{ + uint32_t tag; + + zterp_iff *iff = NULL, *tail = NULL; + + if(zterp_io_seek(io, 0, SEEK_SET) == -1) goto err; + + if(!zterp_io_read32(io, &tag) || tag != STRID("FORM")) goto err; + + if(zterp_io_seek(io, 4, SEEK_CUR) == -1) goto err; + + if(!zterp_io_read32(io, &tag) || tag != STRID(type)) goto err; + + while(zterp_io_read32(io, &tag)) + { + uint32_t size; + zterp_iff *new; + + if(!zterp_io_read32(io, &size)) goto err; + + new = malloc(sizeof *new); + if(new == NULL) goto err; + + new->tag = tag; + new->io = io; + new->offset = zterp_io_tell(io); + new->size = size; + new->next = NULL; + + if(iff == NULL) iff = new; + else tail->next = new; + + tail = new; + + if(new->offset == -1) goto err; + + if(size & 1) size++; + + if(zterp_io_seek(io, size, SEEK_CUR) == -1) goto err; + } + + return iff; + +err: + zterp_iff_free(iff); + + return NULL; +} + +int zterp_iff_find(zterp_iff *iff, const char tag[4], uint32_t *size) +{ + while(iff != NULL) + { + if(iff->tag == STRID(tag)) + { + if(zterp_io_seek(iff->io, iff->offset, SEEK_SET) == -1) return 0; + *size = iff->size; + + return 1; + } + + iff = iff->next; + } + + return 0; +} diff --git a/interpreters/bocfel/iff.h b/interpreters/bocfel/iff.h new file mode 100644 index 0000000..fd63d26 --- /dev/null +++ b/interpreters/bocfel/iff.h @@ -0,0 +1,31 @@ +#ifndef ZTERP_IFF_H +#define ZTERP_IFF_H + +#include + +#include "io.h" + +typedef struct zterp_iff zterp_iff; + +/* Translate an IFF tag into the corresponding 32-bit integer. */ +#define STRID(s) ( \ + (((uint32_t)(s)[0]) << 24) | \ + (((uint32_t)(s)[1]) << 16) | \ + (((uint32_t)(s)[2]) << 8) | \ + (((uint32_t)(s)[3]) << 0) \ + ) + +/* Reverse of above. */ +#define IDSTR(n) ((char[5]){ \ + ((uint32_t)n >> 24) & 0xff, \ + ((uint32_t)n >> 16) & 0xff, \ + ((uint32_t)n >> 8) & 0xff, \ + ((uint32_t)n >> 0) & 0xff, \ + }) + + +void zterp_iff_free(zterp_iff *); +zterp_iff *zterp_iff_parse(zterp_io *, const char [4]); +int zterp_iff_find(zterp_iff *, const char [4], uint32_t *); + +#endif diff --git a/interpreters/bocfel/io.c b/interpreters/bocfel/io.c new file mode 100644 index 0000000..d51e65a --- /dev/null +++ b/interpreters/bocfel/io.c @@ -0,0 +1,493 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#endif + +#include "io.h" +#include "osdep.h" +#include "unicode.h" + +#define MAX_PATH 4096 + +int use_utf8_io; + +/* Generally speaking, UNICODE_LINEFEED (10) is used as a newline. Glk + * requires this (Glk API 0.7.0 §2.2), and when Unicode is available, we + * write characters out by hand even with stdio, so no translation can + * be done. However, when stdio is being used, Unicode is not + * available, and the file usage will be for a transcript or + * command-script, use '\n' as a newline so translation can be done; + * this is the only case where streams are opened in text mode. + * + * zterp_io_stdio() and zterp_io_stdout() are considered text-mode if + * Unicode is not available, binary otherwise. + */ +#define textmode(io) (!use_utf8_io && ((io->mode) & (ZTERP_IO_TRANS | ZTERP_IO_INPUT))) + +struct zterp_io +{ + enum type { IO_STDIO, IO_GLK } type; + + FILE *fp; + int mode; +#ifdef ZTERP_GLK + strid_t file; +#endif +}; + +/* Glk does not like you to be able to pass a full filename to + * glk_fileref_create_by_name(); this means that Glk cannot be used to + * open arbitrary files. However, Glk is still required to prompt for + * files, such as in a save game situation. To allow zterp_io to work + * for opening files both with and without a prompt, it will use stdio + * when either Glk is not available, or when Glk is available but + * prompting is not necessary. + * + * This is needed because the IFF parser is required for both opening + * games (zblorb files) and for saving/restoring. The former needs to + * be able to access any file on the filesystem, and the latter needs to + * prompt. This is a headache. + * + * Prompting is assumed to be necessary if “filename” is NULL. + */ +zterp_io *zterp_io_open(const char *filename, int mode) +{ + zterp_io *io; + char smode[] = "wb"; + + fprintf(stderr, "zterp_io_open: '%s'\n", filename); + + io = malloc(sizeof *io); + if(io == NULL) goto err; + io->mode = mode; + + if (mode & ZTERP_IO_RDONLY) smode[0] = 'r'; + else if(mode & ZTERP_IO_APPEND) smode[0] = 'a'; + + if(textmode(io)) smode[1] = 0; + +#ifdef ZTERP_GLK + int usage = fileusage_BinaryMode, filemode; + + if (mode & ZTERP_IO_SAVE) usage |= fileusage_SavedGame; + else if(mode & ZTERP_IO_TRANS) usage |= fileusage_Transcript; + else if(mode & ZTERP_IO_INPUT) usage |= fileusage_InputRecord; + else usage |= fileusage_Data; + + if (mode & ZTERP_IO_RDONLY) filemode = filemode_Read; + else if(mode & ZTERP_IO_WRONLY) filemode = filemode_Write; + else if(mode & ZTERP_IO_APPEND) filemode = filemode_WriteAppend; + + else goto err; +#else + const char *prompt; + + if (mode & ZTERP_IO_SAVE) prompt = "Enter filename for save game: "; + else if(mode & ZTERP_IO_TRANS) prompt = "Enter filename for transcript: "; + else if(mode & ZTERP_IO_INPUT) prompt = "Enter filename for command record: "; + else prompt = "Enter filename for data: "; +#endif + + /* No need to prompt. */ + if(filename != NULL) + { + io->type = IO_STDIO; + io->fp = fopen(filename, smode); + if(io->fp == NULL) goto err; + } + /* Prompt. */ + else + { +#ifdef ZTERP_GLK + frefid_t ref; + + ref = glk_fileref_create_by_prompt(usage, filemode, 0); + if(ref == NULL) goto err; + + io->type = IO_GLK; + io->file = glk_stream_open_file(ref, filemode, 0); + glk_fileref_destroy(ref); + if(io->file == NULL) goto err; +#else + char fn[MAX_PATH], *p; + + printf("\n%s", prompt); + fflush(stdout); + if(fgets(fn, sizeof fn, stdin) == NULL || fn[0] == '\n') goto err; + p = strchr(fn, '\n'); + if(p != NULL) *p = 0; + + io->type = IO_STDIO; + io->fp = fopen(fn, smode); + if(io->fp == NULL) goto err; +#endif + } + + return io; + +err: + free(io); + + return NULL; +} + +/* The zterp_os_reopen_binary() calls attempt to reopen stdin/stdout as + * binary streams so that reading/writing UTF-8 doesn’t cause unwanted + * translations. The mode of ZTERP_IO_TRANS is set when Unicode is + * unavailable as a way to signal that these are text streams. + */ +const zterp_io *zterp_io_stdin(void) +{ + static zterp_io io; + + if(io.fp == NULL) + { + io.type = IO_STDIO; + io.mode = ZTERP_IO_RDONLY; + if(use_utf8_io) zterp_os_reopen_binary(stdin); + else io.mode |= ZTERP_IO_TRANS; + io.fp = stdin; + } + + return &io; +} + +const zterp_io *zterp_io_stdout(void) +{ + static zterp_io io; + + if(io.fp == NULL) + { + io.type = IO_STDIO; + io.mode = ZTERP_IO_WRONLY; + if(use_utf8_io) zterp_os_reopen_binary(stdout); + else io.mode |= ZTERP_IO_TRANS; + io.fp = stdout; + } + + return &io; +} + +void zterp_io_close(zterp_io *io) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_stream_close(io->file, NULL); + } + else +#endif + { + fclose(io->fp); + } + + free(io); +} + +int zterp_io_seek(const zterp_io *io, long offset, int whence) +{ + /* To smooth over differences between Glk and standard I/O, don’t + * allow seeking in append-only streams. + */ + if(io->mode & ZTERP_IO_APPEND) return -1; + +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_stream_set_position(io->file, offset, whence == SEEK_SET ? seekmode_Start : whence == SEEK_CUR ? seekmode_Current : seekmode_End); + return 0; /* dammit */ + } + else +#endif + { + return fseek(io->fp, offset, whence); + } +} + +long zterp_io_tell(const zterp_io *io) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + return glk_stream_get_position(io->file); + } + else +#endif + { + return ftell(io->fp); + } +} + +/* zterp_io_read() and zterp_io_write() always operate in terms of + * bytes, whether or not Unicode is available. + */ +size_t zterp_io_read(const zterp_io *io, void *buf, size_t n) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glui32 s = glk_get_buffer_stream(io->file, buf, n); + /* This should only happen if io->file is invalid. */ + if(s == (glui32)-1) s = 0; + return s; + } + else +#endif + { + return fread(buf, 1, n, io->fp); + } +} + +size_t zterp_io_write(const zterp_io *io, const void *buf, size_t n) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_put_buffer_stream(io->file, (char *)buf, n); + return n; /* dammit */ + } + else +#endif + { + return fwrite(buf, 1, n, io->fp); + } +} + +int zterp_io_read16(const zterp_io *io, uint16_t *v) +{ + uint8_t buf[2]; + + if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0; + + *v = (buf[0] << 8) | buf[1]; + + return 1; +} + +int zterp_io_read32(const zterp_io *io, uint32_t *v) +{ + uint8_t buf[4]; + + if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0; + + *v = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + + return 1; +} + +/* Read a byte and make sure it’s part of a valid UTF-8 sequence. */ +static int read_byte(const zterp_io *io, uint8_t *c) +{ + if(zterp_io_read(io, c, sizeof *c) != sizeof *c) return 0; + if((*c & 0x80) != 0x80) return 0; + + return 1; +} + +/* zterp_io_getc() and zterp_io_putc() are meant to operate in terms of + * characters, not bytes. That is, unlike C, bytes and characters are + * not equivalent as far as Zterp’s I/O system is concerned. + */ + +/* Read a UTF-8 character, returning it. + * -1 is returned on EOF. + * + * If there is a problem reading the UTF-8 (either from an invalid + * sequence or from a too-large value), a question mark is returned. + * + * If Unicode is not available, read a single byte (assumed to be + * Latin-1). + * If Unicode is not available, IO_STDIO is in use, and text mode is + * set, do newline translation. Text mode is likely to always be + * set—this function really shouldn’t be used in binary mode. + */ +long zterp_io_getc(const zterp_io *io) +{ + long ret; + + if(!use_utf8_io) + { +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + ret = glk_get_char_stream(io->file); + } + else +#endif + { + int c; + + c = getc(io->fp); + if(c == EOF) ret = -1; + else ret = c; + + if(textmode(io) && c == '\n') ret = UNICODE_LINEFEED; + } + } + else + { + uint8_t c; + + if(zterp_io_read(io, &c, sizeof c) != sizeof c) + { + ret = -1; + } + else if((c & 0x80) == 0) /* One byte. */ + { + ret = c; + } + else if((c & 0xe0) == 0xc0) /* Two bytes. */ + { + ret = (c & 0x1f) << 6; + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= (c & 0x3f); + } + else if((c & 0xf0) == 0xe0) /* Three bytes. */ + { + ret = (c & 0x0f) << 12; + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= ((c & 0x3f) << 6); + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= (c & 0x3f); + } + else if((c & 0xf8) == 0xf0) /* Four bytes. */ + { + /* The Z-machine doesn’t support Unicode this large, but at + * least try not to leave a partial character in the stream. + */ + zterp_io_seek(io, 3, SEEK_CUR); + + ret = UNICODE_QUESTIONMARK; + } + else /* Invalid value. */ + { + ret = UNICODE_QUESTIONMARK; + } + } + + if(ret > UINT16_MAX) ret = UNICODE_QUESTIONMARK; + + return ret; +} + +/* Write a Unicode character as UTF-8. + * + * If Unicode is not available, write the value out as a single Latin-1 + * byte. If it is too large for a byte, write out a question mark. + * + * If Unicode is not available, IO_STDIO is in use, and text mode is + * set, do newline translation. + * + * Text mode is likely to always be set—this function really shouldn’t + * be used in binary mode. + */ +void zterp_io_putc(const zterp_io *io, uint16_t c) +{ + if(!use_utf8_io) + { + if(c > UINT8_MAX) c = UNICODE_QUESTIONMARK; +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_put_char_stream(io->file, c); + } + else +#endif + { + if(textmode(io) && c == UNICODE_LINEFEED) c = '\n'; + putc(c, io->fp); + } + } + else + { + uint8_t hi = c >> 8, lo = c & 0xff; + +#define WRITE(c) zterp_io_write(io, &(uint8_t){ c }, sizeof (uint8_t)) + if(c < 128) + { + WRITE(c); + } + else if(c < 2048) + { + WRITE(0xc0 | (hi << 2) | (lo >> 6)); + WRITE(0x80 | (lo & 0x3f)); + } + else + { + WRITE(0xe0 | (hi >> 4)); + WRITE(0x80 | ((hi << 2) & 0x3f) | (lo >> 6)); + WRITE(0x80 | (lo & 0x3f)); + } +#undef WRITE + } +} + +long zterp_io_readline(const zterp_io *io, uint16_t *buf, size_t len) +{ + long ret; + + if(len > LONG_MAX) return -1; + + for(ret = 0; ret < len; ret++) + { + long c = zterp_io_getc(io); + + /* EOF before newline means there was a problem. */ + if(c == -1) return -1; + + /* Don’t count the newline. */ + if(c == UNICODE_LINEFEED) break; + + buf[ret] = c; + } + + return ret; +} + +long zterp_io_filesize(const zterp_io *io) +{ + if(io->type == IO_STDIO && !textmode(io)) + { + return zterp_os_filesize(io->fp); + } + else + { + return -1; + } +} + +void zterp_io_flush(const zterp_io *io) +{ + if(io == NULL || io->type != IO_STDIO || !(io->mode & (ZTERP_IO_WRONLY | ZTERP_IO_APPEND))) return; + + fflush(io->fp); +} diff --git a/interpreters/bocfel/io.h b/interpreters/bocfel/io.h new file mode 100644 index 0000000..c3b9945 --- /dev/null +++ b/interpreters/bocfel/io.h @@ -0,0 +1,40 @@ +#ifndef ZTERP_IO_H +#define ZTERP_IO_H + +#include +#include + +typedef struct zterp_io zterp_io; + +#define ZTERP_IO_DATA 0x00 +#define ZTERP_IO_SAVE 0x01 +#define ZTERP_IO_TRANS 0x02 +#define ZTERP_IO_INPUT 0x04 +#define ZTERP_IO_RDONLY 0x08 +#define ZTERP_IO_WRONLY 0x10 +#define ZTERP_IO_APPEND 0x20 + +/* This variable controls whether the IO system writes UTF-8 or + * Latin-1; it is distinct from Glk’s Unicode setting. + * If this is set, transcripts will be written in UTF-8, and if + * Glk is not being used, screen output will be written in UTF-8. + */ +extern int use_utf8_io; + +zterp_io *zterp_io_open(const char *, int); +const zterp_io *zterp_io_stdin(void); +const zterp_io *zterp_io_stdout(void); +void zterp_io_close(zterp_io *); +int zterp_io_seek(const zterp_io *, long, int); +long zterp_io_tell(const zterp_io *); +size_t zterp_io_read(const zterp_io *, void *, size_t); +size_t zterp_io_write(const zterp_io *, const void *, size_t); +int zterp_io_read16(const zterp_io *, uint16_t *); +int zterp_io_read32(const zterp_io *, uint32_t *); +long zterp_io_getc(const zterp_io *); +void zterp_io_putc(const zterp_io *, uint16_t); +long zterp_io_readline(const zterp_io *, uint16_t *, size_t); +long zterp_io_filesize(const zterp_io *); +void zterp_io_flush(const zterp_io *); + +#endif diff --git a/interpreters/bocfel/math.c b/interpreters/bocfel/math.c new file mode 100644 index 0000000..925391d --- /dev/null +++ b/interpreters/bocfel/math.c @@ -0,0 +1,166 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include + +#include "math.h" +#include "branch.h" +#include "process.h" +#include "stack.h" +#include "util.h" +#include "zterp.h" + +void zinc(void) +{ + store_variable(zargs[0], variable(zargs[0]) + 1); +} + +void zdec(void) +{ + store_variable(zargs[0], variable(zargs[0]) - 1); +} + +void znot(void) +{ + store(~zargs[0]); +} + +void zdec_chk(void) +{ + int16_t new; + int16_t val = zargs[1]; + + zdec(); + + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) new = *stack_top_element(); + else new = variable(zargs[0]); + + branch_if(new < val); +} + +void zinc_chk(void) +{ + int16_t new; + int16_t val = zargs[1]; + + zinc(); + + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) new = *stack_top_element(); + else new = variable(zargs[0]); + + branch_if(new > val); +} + +void ztest(void) +{ + branch_if( (zargs[0] & zargs[1]) == zargs[1] ); +} + +void zor(void) +{ + store(zargs[0] | zargs[1]); +} + +void zand(void) +{ + store(zargs[0] & zargs[1]); +} + +void zadd(void) +{ + store(zargs[0] + zargs[1]); +} + +void zsub(void) +{ + store(zargs[0] - zargs[1]); +} + +void zmul(void) +{ + store(zargs[0] * zargs[1]); +} + +void zdiv(void) +{ + ZASSERT(zargs[1] != 0, "divide by zero"); + store((int16_t)zargs[0] / (int16_t)zargs[1]); +} + +void zmod(void) +{ + ZASSERT(zargs[1] != 0, "divide by zero"); + store((int16_t)zargs[0] % (int16_t)zargs[1]); +} + +void zlog_shift(void) +{ + int16_t places = zargs[1]; + + /* Shifting more than 15 bits is undefined (as of Standard 1.1), but + * do the most sensible thing. + */ + if(places < -15 || places > 15) + { + store(0); + return; + } + + if(places < 0) store(zargs[0] >> -places); + else store(zargs[0] << places); +} + +void zart_shift(void) +{ + int16_t number = zargs[0], places = zargs[1]; + + /* Shifting more than 15 bits is undefined (as of Standard 1.1), but + * do the most sensible thing. + */ + if(places < -15 || places > 15) + { + store(number < 0 ? -1 : 0); + return; + } + + /* Shifting a negative value in C has some consequences: + * • Shifting a negative value left is undefined. + * • Shifting a negative value right is implementation defined. + * + * Thus these are done by hand. The Z-machine requires a right-shift + * of a negative value to propagate the sign bit. This is easily + * accomplished by complementing the value (yielding a positive + * number), shifting it right (zero filling), and complementing again + * (flip the shifted-in zeroes to ones). + * + * For a left-shift, the result should presumably be the same as a + * logical shift, so do that. + */ + if(number < 0) + { + if(places < 0) store(~(~number >> -places)); + else store(zargs[0] << places); + } + else + { + if(places < 0) store(zargs[0] >> -places); + else store(zargs[0] << places); + } +} diff --git a/interpreters/bocfel/math.h b/interpreters/bocfel/math.h new file mode 100644 index 0000000..94c427a --- /dev/null +++ b/interpreters/bocfel/math.h @@ -0,0 +1,20 @@ +#ifndef ZTERP_MATH_H +#define ZTERP_MATH_H + +void zinc(void); +void zdec(void); +void znot(void); +void zdec_chk(void); +void zinc_chk(void); +void ztest(void); +void zor(void); +void zand(void); +void zadd(void); +void zsub(void); +void zmul(void); +void zdiv(void); +void zmod(void); +void zlog_shift(void); +void zart_shift(void); + +#endif diff --git a/interpreters/bocfel/memory.c b/interpreters/bocfel/memory.c new file mode 100644 index 0000000..e234b88 --- /dev/null +++ b/interpreters/bocfel/memory.c @@ -0,0 +1,76 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include + +#include "memory.h" +#include "screen.h" +#include "util.h" +#include "zterp.h" + +uint8_t *memory, *dynamic_memory; +uint32_t memory_size; + +void user_store_byte(uint16_t addr, uint8_t v) +{ + /* If safety checks are off, there’s no point in checking these + * special cases. */ +#ifndef ZTERP_NO_SAFETY_CHECKS +#ifdef ZTERP_TANDY + if(addr == 0x01) + { + ZASSERT(v == BYTE(addr) || (BYTE(addr) ^ v) == 8, "not allowed to modify any bits but 3 at 0x0001"); + } + else +#endif + + /* 0x10 can’t be modified, but let it slide if the story is storing + * the same value that’s already there. This is useful because the + * flags at 0x10 are stored in a word, so the story possibly could use + * @storew at 0x10 to modify the bits in 0x11. + */ + if(addr == 0x10 && BYTE(addr) == v) + { + return; + } + else +#endif + + if(addr == 0x11) + { + ZASSERT((BYTE(addr) ^ v) < 8, "not allowed to modify bits 3-7 at 0x0011"); + + if(!output_stream((v & FLAGS2_TRANSCRIPT) ? OSTREAM_SCRIPT : -OSTREAM_SCRIPT, 0)) v &= ~FLAGS2_TRANSCRIPT; + + header_fixed_font = v & FLAGS2_FIXED; + set_current_style(); + } + + else + { + ZASSERT(addr >= 0x40 && addr < header.static_start, "attempt to write to read-only address 0x%lx", (unsigned long)addr); + } + + STORE_BYTE(addr, v); +} + +void user_store_word(uint16_t addr, uint16_t v) +{ + user_store_byte(addr + 0, v >> 8); + user_store_byte(addr + 1, v & 0xff); +} diff --git a/interpreters/bocfel/memory.h b/interpreters/bocfel/memory.h new file mode 100644 index 0000000..a53dee3 --- /dev/null +++ b/interpreters/bocfel/memory.h @@ -0,0 +1,55 @@ +#ifndef ZTERP_MEMORY_H +#define ZTERP_MEMORY_H + +#include + +#include "util.h" +#include "zterp.h" + +/* Story files do not have access to memory beyond 64K. If they do + * something that would cause such access, wrap appropriately. This is + * the approach Frotz uses (at least for @loadw/@loadb), and is endorsed + * by Andrew Plotkin (see http://www.intfiction.org/forum/viewtopic.php?f=38&t=2052). + * The standard isn’t exactly clear on the issue, and this appears to be + * the most sensible way to deal with the problem. + */ + +extern uint8_t *memory, *dynamic_memory; +extern uint32_t memory_size; + +#define BYTE(addr) (memory[addr]) +#define STORE_BYTE(addr, val) ((void)(memory[addr] = (val))) + +static inline uint16_t WORD(uint32_t addr) +{ +#ifndef ZTERP_NO_CHEAT + uint16_t cheat_val; + if(cheat_find_freezew(addr, &cheat_val)) return cheat_val; +#endif + return (memory[addr] << 8) | memory[addr + 1]; +} + +static inline void STORE_WORD(uint32_t addr, uint16_t val) +{ + memory[addr + 0] = val >> 8; + memory[addr + 1] = val & 0xff; +} + +static inline uint8_t user_byte(uint16_t addr) +{ + ZASSERT(addr < header.static_end, "attempt to read out-of-bounds address 0x%lx", (unsigned long)addr); + + return BYTE(addr); +} + +static inline uint16_t user_word(uint16_t addr) +{ + ZASSERT(addr < header.static_end - 1, "attempt to read out-of-bounds address 0x%lx", (unsigned long)addr); + + return WORD(addr); +} + +void user_store_byte(uint16_t, uint8_t); +void user_store_word(uint16_t, uint16_t); + +#endif diff --git a/interpreters/bocfel/objects.c b/interpreters/bocfel/objects.c new file mode 100644 index 0000000..ae68839 --- /dev/null +++ b/interpreters/bocfel/objects.c @@ -0,0 +1,436 @@ +/*- + * Copyright 2009-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "objects.h" +#include "branch.h" +#include "memory.h" +#include "process.h" +#include "screen.h" +#include "util.h" +#include "zterp.h" + +static uint16_t OBJECT(uint16_t n) +{ + /* Use 32-bit arithmetic to detect 16-bit overflow. */ + uint32_t base = header.objects, obj = n, addr; + int objsize; + + if(zversion <= 3) + { + ZASSERT(n <= 255, "illegal object %u referenced", (unsigned)n); + addr = base + (31 * 2) + (9 * (obj - 1)); + objsize = 9; + } + else + { + addr = base + (63 * 2) + (14 * (obj - 1)); + objsize = 14; + } + + ZASSERT(addr + objsize < header.static_start, "object %u out of range", (unsigned)n); + + return addr; +} + +#define OFFSET_PARENT (zversion <= 3 ? 4 : 6) +#define OFFSET_SIBLING (zversion <= 3 ? 5 : 8) +#define OFFSET_CHILD (zversion <= 3 ? 6 : 10) +#define OFFSET_PROP (zversion <= 3 ? 7 : 12) + +#define PARENT(object) RELATION(object, OFFSET_PARENT) +#define SIBLING(object) RELATION(object, OFFSET_SIBLING) +#define CHILD(object) RELATION(object, OFFSET_CHILD) + +#define SET_PARENT(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_PARENT) +#define SET_SIBLING(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_SIBLING) +#define SET_CHILD(obj1, obj2) SET_OBJECT(obj1, obj2, OFFSET_CHILD) + +static uint16_t PROPADDR(uint16_t n) +{ + return WORD(OBJECT(n) + OFFSET_PROP); +} + +static uint16_t RELATION(uint16_t object, int offset) +{ + return zversion <= 3 ? BYTE(OBJECT(object) + offset) : WORD(OBJECT(object) + offset); +} + +/* + * the 32 attribute flags parent sibling child properties + * ---32 bits in 4 bytes--- ---3 bytes------------------ ---2 bytes-- + * + * the 48 attribute flags parent sibling child properties + * ---48 bits in 6 bytes--- ---3 words, i.e. 6 bytes---- ---2 bytes-- + */ +static void SET_OBJECT(uint16_t obj1, uint16_t obj2, int offset) +{ + if(zversion <= 3) STORE_BYTE(OBJECT(obj1) + offset, obj2); + else STORE_WORD(OBJECT(obj1) + offset, obj2); +} + +static void remove_obj(uint16_t object) +{ + uint16_t parent = PARENT(object); + + if(parent != 0) + { + uint16_t child = CHILD(parent); + + /* Direct child */ + if(child == object) + { + /* parent->child = parent->child->sibling */ + SET_CHILD(parent, SIBLING(child)); + } + else + { + while(SIBLING(child) != object) + { + /* child = child->sibling */ + child = SIBLING(child); + } + + /* Now the sibling of child is the object to remove. */ + + /* child->sibling = child->sibling->sibling */ + SET_SIBLING(child, SIBLING(SIBLING(child))); + } + + /* object->parent = 0 */ + SET_PARENT(object, 0); + + /* object->sibling = 0 */ + SET_SIBLING(object, 0); + } +} + +static uint16_t property_length(uint16_t propaddr) +{ + uint16_t length; + /* The address is to the data; the size byte is right before. */ + uint8_t byte = user_byte(propaddr - 1); + + if(zversion <= 3) + { + length = (byte >> 5) + 1; + } + else + { + if(byte & 0x80) + { + length = byte & 0x3f; + if(length == 0) length = 64; + } + else + { + length = (byte & 0x40) ? 2 : 1; + } + } + + return length; +} + +static uint8_t PROPERTY(uint16_t addr) +{ + uint8_t propnum; + + if(zversion <= 3) + { + propnum = user_byte(addr - 1) & 0x1f; + } + else + { + if(user_byte(addr - 1) & 0x80) propnum = user_byte(addr - 2) & 0x3f; + else propnum = user_byte(addr - 1) & 0x3f; + } + + return propnum; +} + +static uint16_t advance_prop_addr(uint16_t propaddr) +{ + uint8_t size; + + size = user_byte(propaddr++); + + if(size == 0) return 0; + + if(zversion >= 4 && (size & 0x80)) propaddr++; + + return propaddr; +} + +static uint16_t first_property(uint16_t object) +{ + uint16_t propaddr = PROPADDR(object); + + propaddr += (2 * user_byte(propaddr)) + 1; + + return advance_prop_addr(propaddr); +} + +static uint16_t next_property(uint16_t propaddr) +{ + propaddr += property_length(propaddr); + + return advance_prop_addr(propaddr); +} + +#define FOR_EACH_PROPERTY(object, addr) for(uint16_t addr = first_property(object); addr != 0; addr = next_property(addr)) + +static int find_prop(uint16_t object, uint16_t property, uint16_t *propaddr, uint16_t *length) +{ + FOR_EACH_PROPERTY(object, addr) + { + if(PROPERTY(addr) == property) + { + *propaddr = addr; + *length = property_length(addr); + return 1; + } + } + + return 0; +} + +static void check_attr(uint16_t attr) +{ + ZASSERT(attr <= (zversion <= 3 ? 31 : 47), "invalid attribute: %u", (unsigned)attr); +} + +static int is_zero(int is_store, int is_jump) +{ + if(zargs[0] == 0) + { + if(is_store) store(0); + if(is_jump) branch_if(0); + + return 1; + } + + return 0; +} + +#define check_zero(store, jump) do { if(is_zero(store, jump)) return; } while(0) + +/* Attributes are stored at the very beginning of an object, so the + * address OBJECT() returns refers directly to the attributes. The + * leftmost bit is attribute 0. Thus these attribute functions need to + * find out first which byte of the attributes to look at; this is done + * by dividing by 8. Attributes 0-7 will be in byte 0, 8-15 in byte 1, + * and so on. Then the particular bit is found. Attributes 0..7 are + * bits 7..0, attributes 8..15 are 7..0, and so on. Taking the + * remainder of the attribute divided by 8 gives the bit position, + * counting from the left, of the attribute. + */ +#define ATTR_BIT(num) (0x80U >> ((num) % 8)) +void ztest_attr(void) +{ + check_zero(0, 1); + check_attr(zargs[1]); + + uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8); + + branch_if(BYTE(addr) & ATTR_BIT(zargs[1])); +} + +void zset_attr(void) +{ + check_zero(0, 0); + check_attr(zargs[1]); + + uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8); + + STORE_BYTE(addr, BYTE(addr) | ATTR_BIT(zargs[1])); +} + +void zclear_attr(void) +{ + check_zero(0, 0); + check_attr(zargs[1]); + + uint16_t addr = OBJECT(zargs[0]) + (zargs[1] / 8); + + STORE_BYTE(addr, BYTE(addr) & ~ATTR_BIT(zargs[1])); +} +#undef ATTR_BIT + +void zremove_obj(void) +{ + check_zero(0, 0); + + remove_obj(zargs[0]); +} + +void zinsert_obj(void) +{ + check_zero(0, 0); + + remove_obj(zargs[0]); + + SET_SIBLING(zargs[0], CHILD(zargs[1])); + SET_CHILD(zargs[1], zargs[0]); + SET_PARENT(zargs[0], zargs[1]); +} + +void zget_sibling(void) +{ + check_zero(1, 1); + + uint16_t sibling = SIBLING(zargs[0]); + + store(sibling); + branch_if(sibling != 0); +} + +void zget_child(void) +{ + check_zero(1, 1); + + uint16_t child = CHILD(zargs[0]); + + store(child); + branch_if(child != 0); +} + +void zget_parent(void) +{ + check_zero(1, 0); + + store(PARENT(zargs[0])); +} + +void zput_prop(void) +{ + check_zero(0, 0); + + uint16_t propaddr, length; + int found; + + found = find_prop(zargs[0], zargs[1], &propaddr, &length); + + ZASSERT(found, "broken story: no prop"); + ZASSERT(length == 1 || length == 2, "broken story: property too long: %u", (unsigned)length); + + if(length == 1) user_store_byte(propaddr, zargs[2] & 0xff); + else user_store_word(propaddr, zargs[2]); +} + +void zget_prop(void) +{ + check_zero(1, 0); + + uint16_t propaddr, length; + + if(find_prop(zargs[0], zargs[1], &propaddr, &length)) + { + if (length == 1) store(user_byte(propaddr)); + else if(length == 2) store(user_word(propaddr)); + + /* If the length is > 2, the story file is misbehaving. At least + * Christminster does this, and Frotz and Nitfol allow it, reading a + * word, so do that here. + */ + else store(user_word(propaddr)); + } + else + { + uint32_t i; + + ZASSERT(zargs[1] < (zversion <= 3 ? 32 : 64), "invalid property: %u", (unsigned)zargs[1]); + + i = header.objects + (2 * (zargs[1] - 1)); + store(WORD(i)); + } +} + +void zget_prop_len(void) +{ + /* Z-spec 1.1 says @get_prop_len 0 must yield 0. */ + if(zargs[0] == 0) store(0); + else store(property_length(zargs[0])); +} + +void zget_prop_addr(void) +{ + check_zero(1, 0); + + uint16_t propaddr, length; + + if(find_prop(zargs[0], zargs[1], &propaddr, &length)) store(propaddr); + else store(0); +} + +void zget_next_prop(void) +{ + check_zero(1, 0); + + uint16_t object = zargs[0], property = zargs[1]; + int next = 0; + int found_prop = 0; + + FOR_EACH_PROPERTY(object, propaddr) + { + uint8_t propnum = PROPERTY(propaddr); + + if(property == 0 || next) + { + found_prop = propnum; + break; + } + + if(propnum == property) next = 1; + } + + store(found_prop); +} + +void zjin(void) +{ + /* @jin 0 0 is not defined, since @jin requires an object (§15) and + * object 0 is not actually an object (§12.3). However, many + * interpreters yield a true value for this, and Torbjorn Andersson’s + * strictz tester expects it to be true, so go with the flow. + */ + if(zargs[0] == 0 && zargs[1] == 0) + { + branch_if(1); + return; + } + + check_zero(0, 1); + + branch_if(PARENT(zargs[0]) == zargs[1]); +} + +void print_object(uint16_t obj, void (*outc)(uint8_t)) +{ + if(obj == 0) return; + + print_handler(PROPADDR(obj) + 1, outc); +} + +void zprint_obj(void) +{ + check_zero(0, 0); + + print_object(zargs[0], NULL); +} diff --git a/interpreters/bocfel/objects.h b/interpreters/bocfel/objects.h new file mode 100644 index 0000000..eb532ab --- /dev/null +++ b/interpreters/bocfel/objects.h @@ -0,0 +1,24 @@ +#ifndef OBJECTS_H +#define OBJECTS_H + +#include + +void print_object(uint16_t, void (*)(uint8_t)); + +void zget_sibling(void); +void zget_child(void); +void zget_parent(void); +void zremove_obj(void); +void ztest_attr(void); +void zset_attr(void); +void zclear_attr(void); +void zinsert_obj(void); +void zget_prop_len(void); +void zget_prop_addr(void); +void zget_next_prop(void); +void zput_prop(void); +void zget_prop(void); +void zjin(void); +void zprint_obj(void); + +#endif diff --git a/interpreters/bocfel/osdep.c b/interpreters/bocfel/osdep.c new file mode 100644 index 0000000..454c1b2 --- /dev/null +++ b/interpreters/bocfel/osdep.c @@ -0,0 +1,312 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#ifdef ZTERP_UNIX +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include + +#include "osdep.h" +#include "screen.h" + +/* OS-specific functions should all be collected in this file for + * convenience. A sort of poor-man’s “reverse” inheritance is used: for + * each function that a particular operating system provides, it should + * #define a macro of the same name. At the end of the file, a generic + * function is provided for each function that has no associated macro + * definition. + * + * The functions required are as follows: + * + * long zterp_os_filesize(FILE *fp) + * + * Return the size of the file referred to by fp. It is safe to assume + * that the file is opened in binary mode. The file position indicator + * need not be maintained. If the size of the file is larger than + * LONG_MAX, -1 should be returned. + * + * int zterp_os_have_unicode(void) + * + * The main purpose behind this function is to indicate whether + * transcripts will be written in UTF-8 or Latin-1. This is, of course, + * not necessarily an OS matter, but I’ve run into some issues with + * UTF-8 and Windows (at least through Wine), so I want to be as + * sensible as I can with the defaults. The user is able to override + * this value if he so desires. + * If a Glk build is not being used, this function also serves to + * indicate whether all I/O, not just transcripts, should be UTF-8 or + * not. Glk libraries are able to be queried as to their support for + * Unicode so there is no need to make assumptions in that case. + * + * void zterp_os_rcfile(char *s, size_t n) + * + * Different operating systems have different ideas about where + * configuration data should be stored; this function will copy a + * suitable value for the bocfel configuration file into the buffer s + * which is n bytes long. + * + * void zterp_os_reopen_binary(FILE *fp) + * + * Writing UTF-8 requires that no mangling be done, such as might happen + * when a stream is opened in text mode. This function should, if + * necessary, set the mode on the file pointer in fp to be binary. + * + * The following functions are useful for non-Glk builds only. They + * provide for some handling of screen functions that is normally taken + * care of by Glk. + * + * void zterp_os_get_screen_size(unsigned *w, unsigned *h) + * + * The size of the terminal, if known, is written into *w (width) and *h + * (height). If terminal size is unavalable, nothing should be written. + * + * void zterp_os_init_term(void) + * + * If something special needs to be done to prepare the terminal for + * output, it should be done here. This function is called once at + * program startup. + * + * int zterp_os_have_style(int style) + * + * This should return true if the provided style (see style.h for valid + * STYLE_ values) is available. It is safe to assume that styles will + * not be combined; e.g. this will not be called as: + * zterp_os_have_style(STYLE_BOLD | STYLE_ITALIC); + * + * int zterp_os_have_colors(void) + * + * Returns true if the terminal supports colors. + * + * void zterp_os_set_style(int style, int fg, int bg) + * + * Set both a style and foreground/background color. Any previous + * settings should be ignored; for example, if the last call to + * zterp_os_set_style() turned on italics and the current call sets + * bold, the result should be bold, not bold italic. + * Unlike in zterp_os_have_style(), here styles may be combined. See + * the Unix implementation for a reference. + * The colors are Z-machine colors (see §8.3.1), with the following + * note: the only color values that will ever be passed in are 1–9. + */ + +/****************** + * Unix functions * + ******************/ +#ifdef ZTERP_UNIX +#include + +long zterp_os_filesize(FILE *fp) +{ + struct stat buf; + int fd = fileno(fp); + + if(fd == -1 || fstat(fd, &buf) == -1 || !S_ISREG(buf.st_mode) || buf.st_size > LONG_MAX) return -1; + + return buf.st_size; +} +#define zterp_os_filesize + +int zterp_os_have_unicode(void) +{ + return 1; +} +#define zterp_os_have_unicode + +void zterp_os_rcfile(char *s, size_t n) +{ + snprintf(s, n, "%s/.bocfelrc", getenv("HOME") != NULL ? getenv("HOME") : "."); +} +#define zterp_os_rcfile + +#ifndef ZTERP_GLK +#include +#include +#include +#include +#ifdef TIOCGWINSZ +void zterp_os_get_screen_size(unsigned *w, unsigned *h) +{ + struct winsize winsize; + + if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) == 0) + { + *w = winsize.ws_col; + *h = winsize.ws_row; + } +} +#define zterp_os_get_screen_size +#endif + +static const char *ital = NULL, *rev = NULL, *bold = NULL, *none = NULL; +static char *fg_string = NULL, *bg_string = NULL; +static int have_colors = 0; +void zterp_os_init_term(void) +{ + if(setupterm(NULL, STDIN_FILENO, NULL) != OK) return; + + /* prefer italics over underline for emphasized text */ + ital = tgetstr("ZH", NULL); + if(ital == NULL) ital = tgetstr("us", NULL); + rev = tgetstr("mr", NULL); + bold = tgetstr("md", NULL); + none = tgetstr("me", NULL); + + fg_string = tgetstr("AF", NULL); + bg_string = tgetstr("AB", NULL); + + have_colors = none != NULL && fg_string != NULL && bg_string != NULL; +} +#define zterp_os_init_term + +int zterp_os_have_style(int style) +{ + if(none == NULL) return 0; + + if (style == STYLE_ITALIC) return ital != NULL; + else if(style == STYLE_REVERSE) return rev != NULL; + else if(style == STYLE_BOLD) return bold != NULL; + else if(style == STYLE_NONE) return none != NULL; + + return 0; +} +#define zterp_os_have_style + +int zterp_os_have_colors(void) +{ + return have_colors; +} +#define zterp_os_have_colors + +void zterp_os_set_style(int style, int fg, int bg) +{ + /* If the terminal cannot be reset, nothing can be used. */ + if(none == NULL) return; + + putp(none); + + if((style & STYLE_ITALIC) && ital != NULL) putp(ital); + if((style & STYLE_REVERSE) && rev != NULL) putp(rev); + if((style & STYLE_BOLD) && bold != NULL) putp(bold); + + if(have_colors) + { + if(fg > 1) putp(tparm(fg_string, fg - 2, 0, 0, 0, 0, 0, 0, 0, 0)); + if(bg > 1) putp(tparm(bg_string, bg - 2, 0, 0, 0, 0, 0, 0, 0, 0)); + } +} +#define zterp_os_set_style +#endif + +/********************* + * Windows functions * + *********************/ +#elif defined(ZTERP_WIN32) +void zterp_os_rcfile(char *s, size_t n) +{ + char *p; + + p = getenv("APPDATA"); + if(p == NULL) p = getenv("LOCALAPPDATA"); + if(p == NULL) p = "."; + + snprintf(s, n, "%s\\bocfel.ini", p); +} +#define zterp_os_rcfile + +#endif + +/********************* + * Generic functions * + *********************/ +#ifndef zterp_os_filesize +long zterp_os_filesize(FILE *fp) +{ + /* Assume fseek() can seek to the end of binary streams. */ + if(fseek(fp, 0, SEEK_END) == -1) return -1; + + return ftell(fp); +} +#endif + +#ifndef zterp_os_have_unicode +int zterp_os_have_unicode(void) +{ + return 0; +} +#endif + +#ifndef zterp_os_rcfile +void zterp_os_rcfile(char *s, size_t n) +{ + snprintf(s, n, "bocfelrc"); +} +#endif + +/* When UTF-8 output is enabled, special translation of characters (e.g. + * newline) should not be done. Theoretically this function exists to + * set stdin/stdout to binary mode, if necessary. Unix makes no + * text/binary distinction, but Windows does. I’m under the impression + * that there is a setmode() function that should be able to do this, + * but my knowledge of Windows is so small that I do not want to do much + * more than I have, lest I completely break Windows support—assuming it + * even works. + * freopen() should be able to do this, but with my testing under Wine, + * no text gets output in such a case. + */ +#ifndef zterp_os_reopen_binary +void zterp_os_reopen_binary(FILE *fp) +{ +} +#endif + +#ifndef ZTERP_GLK +#ifndef zterp_os_get_screen_size +void zterp_os_get_screen_size(unsigned *w, unsigned *h) +{ +} +#endif + +#ifndef zterp_os_init_term +void zterp_os_init_term(void) +{ +} +#endif + +#ifndef zterp_os_have_style +int zterp_os_have_style(int style) +{ + return 0; +} +#endif + +#ifndef zterp_os_have_colors +int zterp_os_have_colors(void) +{ + return 0; +} +#endif + +#ifndef zterp_os_set_style +void zterp_os_set_style(int style, int fg, int bg) +{ +} +#endif +#endif diff --git a/interpreters/bocfel/osdep.h b/interpreters/bocfel/osdep.h new file mode 100644 index 0000000..de8f062 --- /dev/null +++ b/interpreters/bocfel/osdep.h @@ -0,0 +1,20 @@ +#ifndef ZTERP_OSDEP_H +#define ZTERP_OSDEP_H + +#include +#include + +long zterp_os_filesize(FILE *); +int zterp_os_have_unicode(void); +void zterp_os_rcfile(char *, size_t); +void zterp_os_reopen_binary(FILE *); + +#ifndef ZTERP_GLK +void zterp_os_get_screen_size(unsigned *, unsigned *); +void zterp_os_init_term(void); +int zterp_os_have_style(int); +int zterp_os_have_colors(void); +void zterp_os_set_style(int, int, int); +#endif + +#endif diff --git a/interpreters/bocfel/process.c b/interpreters/bocfel/process.c new file mode 100644 index 0000000..385aee3 --- /dev/null +++ b/interpreters/bocfel/process.c @@ -0,0 +1,416 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#endif + +#include "process.h" +#include "branch.h" +#include "dict.h" +#include "math.h" +#include "memory.h" +#include "objects.h" +#include "random.h" +#include "screen.h" +#include "stack.h" +#include "table.h" +#include "util.h" +#include "zoom.h" +#include "zterp.h" + +uint16_t zargs[8]; +int znargs; + +static jmp_buf *jumps; +static size_t njumps; + +/* Each time an interrupt happens, process_instructions() is called + * (effectively starting a whole new round of interpreting). This + * variable holds the current level of interpreting: 0 for no + * interrupts, 1 if one interrupt has been called, 2 if an interrupt was + * called inside of an interrupt, and so on. + */ +static long ilevel = -1; + +long interrupt_level(void) +{ + return ilevel; +} + +/* When this is called, the interrupt at level “level” will stop + * running: if a single interrupt is running, then break_from(1) will + * stop the interrupt, going back to the main program. Breaking from + * interrupt level 0 (which is not actually an interrupt) will end the + * program. This is how @quit is implemented. + */ +void break_from(long level) +{ + ilevel = level - 1; + longjmp(jumps[level], 1); +} + +/* If a restore happens inside of an interrupt, the level needs to be + * set back to 0, but without a longjmp(), so break_from() cannot be + * used. + */ +void reset_level(void) +{ + ilevel = 0; +} + +/* To signal a restart, longjmp() is called with 2; this advises + * process_instructions() to restart the story file and then continue + * execution, whereas a value of 1 tells it to return immediately. + */ +static void zrestart(void) +{ + ilevel = 0; + longjmp(jumps[0], 2); +} + +/* Returns 1 if decoded, 0 otherwise (omitted) */ +static int decode_base(uint8_t type, uint16_t *loc) +{ + if (type == 0) *loc = WORD(pc++); /* Large constant. */ + else if(type == 1) *loc = BYTE(pc); /* Small constant. */ + else if(type == 2) *loc = variable(BYTE(pc)); /* Variable. */ + else return 0; /* Omitted. */ + + pc++; + + return 1; +} + +static void decode_var(uint8_t types) +{ + uint16_t ret; + + if(!decode_base((types >> 6) & 0x03, &ret)) return; + zargs[znargs++] = ret; + if(!decode_base((types >> 4) & 0x03, &ret)) return; + zargs[znargs++] = ret; + if(!decode_base((types >> 2) & 0x03, &ret)) return; + zargs[znargs++] = ret; + if(!decode_base((types >> 0) & 0x03, &ret)) return; + zargs[znargs++] = ret; +} + +/* op[0] is 0OP, op[1] is 1OP, etc */ +static void (*op[5][256])(void); +static const char *opnames[5][256]; +enum { ZERO, ONE, TWO, VAR, EXT }; + +#define op_call(opt, opnumber) (op[opt][opnumber]()) + +/* This nifty trick is from Frotz. */ +static void zextended(void) +{ + uint8_t opnumber = BYTE(pc++); + + decode_var(BYTE(pc++)); + + /* §14.2.1 + * The exception for 0x80–0x83 is for the Zoom extensions. + * Standard 1.1 implicitly updates §14.2.1 to recommend ignoring + * opcodes in the range EXT:30 to EXT:255, due to the fact that + * @buffer_screen is EXT:29. + */ + if(opnumber > 0x1d && (opnumber < 0x80 || opnumber > 0x83)) return; + + op_call(EXT, opnumber); +} + +static void illegal_opcode(void) +{ +#ifndef ZTERP_NO_SAFETY_CHECKS + die("illegal opcode (pc = 0x%lx)", zassert_pc); +#else + die("illegal opcode"); +#endif +} + +void setup_opcodes(void) +{ + for(int i = 0; i < 5; i++) + { + for(int j = 0; j < 256; j++) + { + op[i][j] = illegal_opcode; + } + } +#define OP(args, opcode, fn) do { op[args][opcode] = fn; opnames[args][opcode] = #fn; } while(0) + OP(ZERO, 0x00, zrtrue); + OP(ZERO, 0x01, zrfalse); + OP(ZERO, 0x02, zprint); + OP(ZERO, 0x03, zprint_ret); + OP(ZERO, 0x04, znop); + if(zversion <= 4) OP(ZERO, 0x05, zsave); + if(zversion <= 4) OP(ZERO, 0x06, zrestore); + OP(ZERO, 0x07, zrestart); + OP(ZERO, 0x08, zret_popped); + if(zversion <= 4) OP(ZERO, 0x09, zpop); + else OP(ZERO, 0x09, zcatch); + OP(ZERO, 0x0a, zquit); + OP(ZERO, 0x0b, znew_line); + if (zversion == 3) OP(ZERO, 0x0c, zshow_status); + else if(zversion >= 4) OP(ZERO, 0x0c, znop); /* §15: Technically illegal in V4+, but a V5 Wishbringer accidentally uses this opcode. */ + if(zversion >= 3) OP(ZERO, 0x0d, zverify); + if(zversion >= 5) OP(ZERO, 0x0e, zextended); + if(zversion >= 5) OP(ZERO, 0x0f, zpiracy); + + OP(ONE, 0x00, zjz); + OP(ONE, 0x01, zget_sibling); + OP(ONE, 0x02, zget_child); + OP(ONE, 0x03, zget_parent); + OP(ONE, 0x04, zget_prop_len); + OP(ONE, 0x05, zinc); + OP(ONE, 0x06, zdec); + OP(ONE, 0x07, zprint_addr); + if(zversion >= 4) OP(ONE, 0x08, zcall_1s); + OP(ONE, 0x09, zremove_obj); + OP(ONE, 0x0a, zprint_obj); + OP(ONE, 0x0b, zret); + OP(ONE, 0x0c, zjump); + OP(ONE, 0x0d, zprint_paddr); + OP(ONE, 0x0e, zload); + if(zversion <= 4) OP(ONE, 0x0f, znot); + else OP(ONE, 0x0f, zcall_1n); + + OP(TWO, 0x01, zje); + OP(TWO, 0x02, zjl); + OP(TWO, 0x03, zjg); + OP(TWO, 0x04, zdec_chk); + OP(TWO, 0x05, zinc_chk); + OP(TWO, 0x06, zjin); + OP(TWO, 0x07, ztest); + OP(TWO, 0x08, zor); + OP(TWO, 0x09, zand); + OP(TWO, 0x0a, ztest_attr); + OP(TWO, 0x0b, zset_attr); + OP(TWO, 0x0c, zclear_attr); + OP(TWO, 0x0d, zstore); + OP(TWO, 0x0e, zinsert_obj); + OP(TWO, 0x0f, zloadw); + OP(TWO, 0x10, zloadb); + OP(TWO, 0x11, zget_prop); + OP(TWO, 0x12, zget_prop_addr); + OP(TWO, 0x13, zget_next_prop); + OP(TWO, 0x14, zadd); + OP(TWO, 0x15, zsub); + OP(TWO, 0x16, zmul); + OP(TWO, 0x17, zdiv); + OP(TWO, 0x18, zmod); + if(zversion >= 4) OP(TWO, 0x19, zcall_2s); + if(zversion >= 5) OP(TWO, 0x1a, zcall_2n); + if(zversion >= 5) OP(TWO, 0x1b, zset_colour); + if(zversion >= 5) OP(TWO, 0x1c, zthrow); + + OP(VAR, 0x00, zcall); + OP(VAR, 0x01, zstorew); + OP(VAR, 0x02, zstoreb); + OP(VAR, 0x03, zput_prop); + OP(VAR, 0x04, zread); + OP(VAR, 0x05, zprint_char); + OP(VAR, 0x06, zprint_num); + OP(VAR, 0x07, zrandom); + OP(VAR, 0x08, zpush); + OP(VAR, 0x09, zpull); + if(zversion >= 3) OP(VAR, 0x0a, zsplit_window); + if(zversion >= 3) OP(VAR, 0x0b, zset_window); + if(zversion >= 4) OP(VAR, 0x0c, zcall_vs2); + if(zversion >= 4) OP(VAR, 0x0d, zerase_window); + if(zversion >= 4) OP(VAR, 0x0e, zerase_line); + if(zversion >= 4) OP(VAR, 0x0f, zset_cursor); + if(zversion >= 4) OP(VAR, 0x10, zget_cursor); + if(zversion >= 4) OP(VAR, 0x11, zset_text_style); + if(zversion >= 4) OP(VAR, 0x12, znop); /* XXX buffer_mode */ + if(zversion >= 3) OP(VAR, 0x13, zoutput_stream); + if(zversion >= 3) OP(VAR, 0x14, zinput_stream); + if(zversion >= 3) OP(VAR, 0x15, zsound_effect); + if(zversion >= 4) OP(VAR, 0x16, zread_char); + if(zversion >= 4) OP(VAR, 0x17, zscan_table); + if(zversion >= 5) OP(VAR, 0x18, znot); + if(zversion >= 5) OP(VAR, 0x19, zcall_vn); + if(zversion >= 5) OP(VAR, 0x1a, zcall_vn2); + if(zversion >= 5) OP(VAR, 0x1b, ztokenise); + if(zversion >= 5) OP(VAR, 0x1c, zencode_text); + if(zversion >= 5) OP(VAR, 0x1d, zcopy_table); + if(zversion >= 5) OP(VAR, 0x1e, zprint_table); + if(zversion >= 5) OP(VAR, 0x1f, zcheck_arg_count); + + if(zversion >= 5) OP(EXT, 0x00, zsave5); + if(zversion >= 5) OP(EXT, 0x01, zrestore5); + if(zversion >= 5) OP(EXT, 0x02, zlog_shift); + if(zversion >= 5) OP(EXT, 0x03, zart_shift); + if(zversion >= 5) OP(EXT, 0x04, zset_font); + if(zversion >= 6) OP(EXT, 0x05, znop); /* XXX draw_picture */ + if(zversion >= 6) OP(EXT, 0x06, zpicture_data); + if(zversion >= 6) OP(EXT, 0x07, znop); /* XXX erase_picture */ + if(zversion >= 6) OP(EXT, 0x08, znop); /* XXX set_margins */ + if(zversion >= 5) OP(EXT, 0x09, zsave_undo); + if(zversion >= 5) OP(EXT, 0x0a, zrestore_undo); + if(zversion >= 5) OP(EXT, 0x0b, zprint_unicode); + if(zversion >= 5) OP(EXT, 0x0c, zcheck_unicode); + if(zversion >= 5) OP(EXT, 0x0d, zset_true_colour); + if(zversion >= 6) OP(EXT, 0x10, znop); /* XXX move_window */ + if(zversion >= 6) OP(EXT, 0x11, znop); /* XXX window_size */ + if(zversion >= 6) OP(EXT, 0x12, znop); /* XXX window_style */ + if(zversion >= 6) OP(EXT, 0x13, zget_wind_prop); + if(zversion >= 6) OP(EXT, 0x14, znop); /* XXX scroll_window */ + if(zversion >= 6) OP(EXT, 0x15, zpop_stack); + if(zversion >= 6) OP(EXT, 0x16, znop); /* XXX read_mouse */ + if(zversion >= 6) OP(EXT, 0x17, znop); /* XXX mouse_window */ + if(zversion >= 6) OP(EXT, 0x18, zpush_stack); + if(zversion >= 6) OP(EXT, 0x19, znop); /* XXX put_wind_prop */ + if(zversion >= 6) OP(EXT, 0x1a, zprint_form); + if(zversion >= 6) OP(EXT, 0x1b, zmake_menu); + if(zversion >= 6) OP(EXT, 0x1c, znop); /* XXX picture_table */ + if(zversion >= 6) OP(EXT, 0x1d, zbuffer_screen); + + /* Zoom extensions. */ + OP(EXT, 0x80, zstart_timer); + OP(EXT, 0x81, zstop_timer); + OP(EXT, 0x82, zread_timer); + OP(EXT, 0x83, zprint_timer); +#undef OP +} + +void process_instructions(void) +{ + if(njumps <= ++ilevel) + { + jumps = realloc(jumps, ++njumps * sizeof *jumps); + if(jumps == NULL) die("unable to allocate memory for jump buffer"); + } + + switch(setjmp(jumps[ilevel])) + { + case 1: /* Normal break from interrupt. */ + return; + case 2: /* Special break: a restart was requested. */ + { + /* §6.1.3: Flags2 is preserved on a restart. */ + uint16_t flags2 = WORD(0x10); + + process_story(); + + STORE_WORD(0x10, flags2); + } + break; + } + + while(1) + { + uint8_t opcode; + +#if defined(ZTERP_GLK) && defined(ZTERP_GLK_TICK) + glk_tick(); +#endif + + ZPC(pc); + + opcode = BYTE(pc++); + + /* long 2OP */ + if(opcode < 0x80) + { + znargs = 2; + + if(opcode & 0x40) zargs[0] = variable(BYTE(pc++)); + else zargs[0] = BYTE(pc++); + + if(opcode & 0x20) zargs[1] = variable(BYTE(pc++)); + else zargs[1] = BYTE(pc++); + + op_call(TWO, opcode & 0x1f); + } + + /* short 1OP */ + else if(opcode < 0xb0) + { + znargs = 1; + + if(opcode < 0x90) /* large constant */ + { + zargs[0] = WORD(pc); + pc += 2; + } + else if(opcode < 0xa0) /* small constant */ + { + zargs[0] = BYTE(pc++); + } + else /* variable */ + { + zargs[0] = variable(BYTE(pc++)); + } + + op_call(ONE, opcode & 0x0f); + } + + /* short 0OP (plus EXT) */ + else if(opcode < 0xc0) + { + znargs = 0; + + op_call(ZERO, opcode & 0x0f); + } + + /* variable 2OP */ + else if(opcode < 0xe0) + { + znargs = 0; + + decode_var(BYTE(pc++)); + + op_call(TWO, opcode & 0x1f); + } + + /* Double variable VAR */ + else if(opcode == 0xec || opcode == 0xfa) + { + uint8_t types1, types2; + + znargs = 0; + + types1 = BYTE(pc++); + types2 = BYTE(pc++); + decode_var(types1); + decode_var(types2); + + op_call(VAR, opcode & 0x1f); + } + + /* variable VAR */ + else + { + znargs = 0; + + read_pc = pc - 1; + + decode_var(BYTE(pc++)); + + op_call(VAR, opcode & 0x1f); + } + } +} diff --git a/interpreters/bocfel/process.h b/interpreters/bocfel/process.h new file mode 100644 index 0000000..e98b2c8 --- /dev/null +++ b/interpreters/bocfel/process.h @@ -0,0 +1,16 @@ +#ifndef ZTERP_PROCESS_h +#define ZTERP_PROCESS_H + +#include + +extern uint16_t zargs[]; +extern int znargs; + +void break_from(long); +void reset_level(void); +long interrupt_level(void); + +void setup_opcodes(void); +void process_instructions(void); + +#endif diff --git a/interpreters/bocfel/random.c b/interpreters/bocfel/random.c new file mode 100644 index 0000000..2d15347 --- /dev/null +++ b/interpreters/bocfel/random.c @@ -0,0 +1,162 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "random.h" +#include "process.h" +#include "zterp.h" + +/* Mersenne Twister. */ +static uint32_t mt[624]; +static uint32_t mt_idx = 0; + +static void zterp_srand(uint32_t s) +{ + mt[0] = s; + for(int i = 1; i < 624; i++) + { + mt[i] = 1812433253UL * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i; + } + mt_idx = 0; +} + +static void mt_gen_num(void) +{ + for(int i = 0; i < 624; i++) + { + uint32_t y; + + y = mt[i] & 0x80000000UL; + y |= (mt[(i + 1) % 624]) & 0x7fffffffUL; + + mt[i] = mt[(i + 397) % 624] ^ (y >> 1); + + if(y % 2 == 1) mt[i] ^= 2567483615UL; + } +} + +static uint32_t zterp_rand(void) +{ + uint32_t y; + + if(mt_idx == 0) mt_gen_num(); + + y = mt[mt_idx]; + y ^= (y >> 11); + y ^= (y << 7) & 2636928640UL; + y ^= (y << 15) & 4022730752UL; + y ^= (y >> 18); + + mt_idx = (mt_idx + 1) % 624; + + return y; +} + +static int rng_interval = 0; +static int rng_counter = 0; + +/* Called with 0, seed the PRNG with either + * a) a user-provided seed (via -z) if available, or + * b) a seed read from a user-provided file/device (via -Z) if + * available, or + * c) a seed derived from a hash of the constituent bytes of the value + * returned by time(NULL) + * + * Called with a value 0 < S < 1000, generate a string of numbers 1, 2, + * 3, ..., S, 1, 2, 3, ... S, ... as recommended in the remarks to §2. + * + * Called with a value >= 1000, use that value as a normal seed. + */ +void seed_random(long value) +{ + if(value == 0) + { + if(options.random_seed == -1) + { + time_t t = time(NULL); + unsigned char *p = (unsigned char *)&t; + uint32_t s = 0; + + /* time_t hashing based on code by Lawrence Kirby. */ + for(size_t i = 0; i < sizeof t; i++) s = s * (UCHAR_MAX + 2U) + p[i]; + + if(options.random_device != NULL) + { + FILE *fp; + uint32_t temp; + + fp = fopen(options.random_device, "r"); + if(fp != NULL) + { + if(fread(&temp, sizeof temp, 1, fp) == 1) s = temp; + fclose(fp); + } + } + + zterp_srand(s); + } + else + { + zterp_srand(options.random_seed); + } + + rng_interval = 0; + } + else if(value < 1000) + { + rng_counter = 0; + rng_interval = value; + } + else + { + zterp_srand(value); + rng_interval = 0; + } +} + +void zrandom(void) +{ + long v = (int16_t)zargs[0]; + + if(v <= 0) + { + seed_random(-v); + store(0); + } + else + { + uint32_t res; + + if(rng_interval != 0) + { + res = rng_counter++; + if(rng_counter == rng_interval) rng_counter = 0; + } + else + { + res = zterp_rand(); + } + + store(res % zargs[0] + 1); + } +} diff --git a/interpreters/bocfel/random.h b/interpreters/bocfel/random.h new file mode 100644 index 0000000..5968432 --- /dev/null +++ b/interpreters/bocfel/random.h @@ -0,0 +1,8 @@ +#ifndef ZTERP_MT_H +#define ZTERP_MT_H + +void seed_random(long); + +void zrandom(void); + +#endif diff --git a/interpreters/bocfel/screen.c b/interpreters/bocfel/screen.c new file mode 100644 index 0000000..35feb56 --- /dev/null +++ b/interpreters/bocfel/screen.c @@ -0,0 +1,2475 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#include +#endif + +#include "screen.h" +#include "branch.h" +#include "dict.h" +#include "io.h" +#include "memory.h" +#include "objects.h" +#include "osdep.h" +#include "process.h" +#include "stack.h" +#include "unicode.h" +#include "util.h" +#include "zterp.h" + +static struct window +{ + unsigned style; + + enum font { FONT_NONE = -1, FONT_PREVIOUS, FONT_NORMAL, FONT_PICTURE, FONT_CHARACTER, FONT_FIXED } font; + enum font prev_font; + +#ifdef ZTERP_GLK + winid_t id; + long x, y; /* Only meaningful for window 1 */ + int pending_read; + union line + { + char latin1[256]; + glui32 unicode[256]; + } *line; + int has_echo; +#endif +} windows[8], *mainwin = &windows[0], *curwin = &windows[0]; +#ifdef ZTERP_GLK +static struct window *upperwin = &windows[1]; +static struct window statuswin; +static long upper_window_height = 0; +static long upper_window_width = 0; +static winid_t errorwin; +#endif + +/* In all versions but 6, styles are global and stored in mainwin. For + * V6, styles are tracked per window and thus stored in each individual + * window. For convenience, this macro expands to the “style window” + * for any version. + */ +#define style_window (zversion == 6 ? curwin : mainwin) + +/* If the window needs to be temporarily switched (@show_status and + * @print_form print to specific windows, and window_change() might + * temporarily need to switch to the upper window), the code that + * requires a specific window can be wrapped in these macros. + */ +#ifdef ZTERP_GLK +#define SWITCH_WINDOW_START(win) { struct window *saved_ = curwin; curwin = (win); glk_set_window((win)->id); +#define SWITCH_WINDOW_END() curwin = saved_; glk_set_window(curwin->id); } +#else +#define SWITCH_WINDOW_START(win) { struct window *saved_ = curwin; curwin = (win); +#define SWITCH_WINDOW_END() curwin = saved_; } +#endif + +/* Output stream bits. */ +#define STREAM_SCREEN (1U << 1) +#define STREAM_TRANS (1U << 2) +#define STREAM_MEMORY (1U << 3) +#define STREAM_SCRIPT (1U << 4) + +static unsigned int streams = STREAM_SCREEN; +static zterp_io *transio, *scriptio; + +static struct +{ + uint16_t table; + uint16_t i; +} stables[16]; +static int stablei = -1; + +static int istream = ISTREAM_KEYBOARD; +static zterp_io *istreamio; + +struct input +{ + enum { INPUT_CHAR, INPUT_LINE } type; + + /* ZSCII value of key read for @read_char. */ + uint8_t key; + + /* Unicode line of chars read for @read. */ + uint32_t *line; + uint8_t maxlen; + uint8_t len; + uint8_t preloaded; + + /* Character used to terminate input. If terminating keys are not + * supported by the Glk implementation being used (or if Glk is not + * used at all) this will be ZSCII_NEWLINE; or in the case of + * cancellation, 0. + */ + uint8_t term; +}; + +/* This macro makes it so that code elsewhere needn’t check have_unicode before printing. */ +#define GLK_PUT_CHAR(c) do { if(!have_unicode) glk_put_char(unicode_to_latin1[c]); else glk_put_char_uni(c); } while(0) + +void show_message(const char *fmt, ...) +{ + va_list ap; + char message[1024]; + + va_start(ap, fmt); + vsnprintf(message, sizeof message, fmt, ap); + va_end(ap); + +#ifdef ZTERP_GLK + static int error_lines = 0; + + if(errorwin != NULL) + { + glui32 w, h; + + /* Allow multiple messages to stack, but force at least 5 lines to + * always be visible in the main window. This is less than perfect + * because it assumes that each message will be less than the width + * of the screen, but it’s not a huge deal, really; even if the + * lines are too long, at least Gargoyle and glktermw are graceful + * enough. + */ + glk_window_get_size(mainwin->id, &w, &h); + + if(h > 5) glk_window_set_arrangement(glk_window_get_parent(errorwin), winmethod_Below | winmethod_Fixed, ++error_lines, errorwin); + glk_put_char_stream(glk_window_get_stream(errorwin), UNICODE_LINEFEED); + } + else + { + errorwin = glk_window_open(mainwin->id, winmethod_Below | winmethod_Fixed, error_lines = 2, wintype_TextBuffer, 0); + } + + /* If windows are not supported (e.g. in cheapglk), messages will not + * get displayed. If this is the case, print to the main window. + */ + if(errorwin != NULL) + { + glk_set_style_stream(glk_window_get_stream(errorwin), style_Alert); + glk_put_string_stream(glk_window_get_stream(errorwin), message); + } + else + { + SWITCH_WINDOW_START(mainwin); + glk_put_string("\12["); + glk_put_string(message); + glk_put_string("]\12"); + SWITCH_WINDOW_END(); + } +#else + /* In Glk messages go to a separate window, but they're interleaved in + * non-Glk. Put brackets around the message in an attempt to offset + * it from the game a bit. + */ + fprintf(stderr, "\n[%s]\n", message); +#endif +} + +/* See §7. + * This returns true if the stream was successfully selected. + * Deselecting a stream is always successful. + */ +int output_stream(int16_t number, uint16_t table) +{ + if(number > 0) + { + streams |= 1U << number; + } + else if(number < 0) + { + if(number != -3 || stablei == 0) streams &= ~(1U << -number); + } + + if(number == 2) + { + STORE_WORD(0x10, WORD(0x10) | FLAGS2_TRANSCRIPT); + if(transio == NULL) + { + transio = zterp_io_open(options.transcript_name, ZTERP_IO_TRANS | (options.overwrite_transcript ? ZTERP_IO_WRONLY : ZTERP_IO_APPEND)); + if(transio == NULL) + { + STORE_WORD(0x10, WORD(0x10) & ~FLAGS2_TRANSCRIPT); + streams &= ~STREAM_TRANS; + warning("unable to open the transcript"); + } + } + } + else if(number == -2) + { + STORE_WORD(0x10, WORD(0x10) & ~FLAGS2_TRANSCRIPT); + } + + if(number == 3) + { + stablei++; + ZASSERT(stablei < 16, "too many stream tables"); + + stables[stablei].table = table; + user_store_word(stables[stablei].table, 0); + stables[stablei].i = 2; + } + else if(number == -3 && stablei >= 0) + { + user_store_word(stables[stablei].table, stables[stablei].i - 2); + stablei--; + } + + if(number == 4) + { + if(scriptio == NULL) + { + scriptio = zterp_io_open(options.record_name, ZTERP_IO_WRONLY | ZTERP_IO_INPUT); + if(scriptio == NULL) + { + streams &= ~STREAM_SCRIPT; + warning("unable to open the script"); + } + } + } + /* XXX v6 has even more handling */ + + return number < 0 || (streams & (1U << number)); +} + +void zoutput_stream(void) +{ + output_stream(zargs[0], zargs[1]); +} + +/* See §10. + * This returns true if the stream was successfully selected. + */ +int input_stream(int which) +{ + istream = which; + + if(istream == ISTREAM_KEYBOARD) + { + if(istreamio != NULL) + { + zterp_io_close(istreamio); + istreamio = NULL; + } + } + else if(istream == ISTREAM_FILE) + { + if(istreamio == NULL) + { + istreamio = zterp_io_open(options.replay_name, ZTERP_IO_INPUT | ZTERP_IO_RDONLY); + if(istreamio == NULL) + { + warning("unable to open the command script"); + istream = ISTREAM_KEYBOARD; + } + } + } + else + { + ZASSERT(0, "invalid input stream: %d", istream); + } + + return istream == which; +} + +void zinput_stream(void) +{ + input_stream(zargs[0]); +} + +/* This does not even pretend to understand V6 windows. */ +static void set_current_window(struct window *window) +{ + curwin = window; + +#ifdef ZTERP_GLK + if(curwin == upperwin && upperwin->id != NULL) + { + upperwin->x = upperwin->y = 0; + glk_window_move_cursor(upperwin->id, 0, 0); + } + + glk_set_window(curwin->id); +#endif + + set_current_style(); +} + +/* Find and validate a window. If window is -3 and the story is V6, + * return the current window. + */ +static struct window *find_window(uint16_t window) +{ + int16_t w = window; + + ZASSERT(zversion == 6 ? w == -3 || (w >= 0 && w < 8) : w == 0 || w == 1, "invalid window selected: %d", w); + + if(w == -3) return curwin; + + return &windows[w]; +} + +#ifdef ZTERP_GLK +/* When resizing the upper window, the screen’s contents should not + * change (§8.6.1); however, the way windows are handled with Glk makes + * this slightly impossible. When an Inform game tries to display + * something with “box”, it expands the upper window, displays the quote + * box, and immediately shrinks the window down again. This is a + * problem under Glk because the window immediately disappears. Other + * games, such as Bureaucracy, expect the upper window to shrink as soon + * as it has been requested. Thus the following system is used: + * + * If a request is made to shrink the upper window, it is granted + * immediately if there has been user input since the last window resize + * request. If there has not been user input, the request is delayed + * until after the next user input is read. + */ +static long delayed_window_shrink = -1; +static int saw_input; + +static void update_delayed(void) +{ + glui32 height; + + if(delayed_window_shrink == -1 || upperwin->id == NULL) return; + + glk_window_set_arrangement(glk_window_get_parent(upperwin->id), winmethod_Above | winmethod_Fixed, delayed_window_shrink, upperwin->id); + upper_window_height = delayed_window_shrink; + + /* Glk might resize the window to a smaller height than was requested, + * so track the actual height, not the requested height. + */ + glk_window_get_size(upperwin->id, NULL, &height); + if(height != upper_window_height) + { + /* This message probably won’t be seen in a window since the upper + * window is likely covering everything, but try anyway. + */ + show_message("Unable to fulfill window size request: wanted %ld, got %lu", delayed_window_shrink, (unsigned long)height); + upper_window_height = height; + } + + delayed_window_shrink = -1; +} + +/* Both the upper and lower windows have their own issues to deal with + * when there is line input. This function ensures that the cursor + * position is properly tracked in the upper window, and if possible, + * aids in the suppression of newline printing on input cancellation in + * the lower window. + */ +static void cleanup_screen(struct input *input) +{ + if(input->type != INPUT_LINE) return; + + /* If the current window is the upper window, the position of the + * cursor needs to be tracked, so after a line has successfully been + * read, advance the cursor to the initial position of the next line, + * or if a terminating key was used or input was canceled, to the end + * of the input. + */ + if(curwin == upperwin) + { + if(input->term != ZSCII_NEWLINE) upperwin->x += input->len; + + if(input->term == ZSCII_NEWLINE || upperwin->x >= upper_window_width) + { + upperwin->x = 0; + if(upperwin->y < upper_window_height) upperwin->y++; + } + + glk_window_move_cursor(upperwin->id, upperwin->x, upperwin->y); + } + + /* If line input echoing is turned off, newlines will not be printed + * when input is canceled, but neither will the input line. Fix that. + */ + if(curwin->has_echo) + { + glk_set_style(style_Input); + for(int i = 0; i < input->len; i++) GLK_PUT_CHAR(input->line[i]); + if(input->term == ZSCII_NEWLINE) glk_put_char(UNICODE_LINEFEED); + set_current_style(); + } +} + +/* In an interrupt, if the story tries to read or write, the previous + * read event (which triggered the interrupt) needs to be canceled. + * This function does the cancellation. + */ +static void cancel_read_events(struct window *window) +{ + if(window->pending_read) + { + event_t ev; + + glk_cancel_char_event(window->id); + glk_cancel_line_event(window->id, &ev); + + /* If the pending read was a line input, zero terminate the string + * so when it’s re-requested the length of the already-loaded + * portion can be discovered. Also deal with cursor positioning in + * the upper window, and line echoing in the lower window. + */ + if(ev.type == evtype_LineInput && window->line != NULL) + { + uint32_t line[ev.val1]; + struct input input = { .type = INPUT_LINE, .line = line, .term = 0, .len = ev.val1 }; + + if(have_unicode) window->line->unicode[ev.val1] = 0; + else window->line->latin1 [ev.val1] = 0; + + for(int i = 0; i < input.len; i++) + { + if(have_unicode) line[i] = window->line->unicode[i]; + else line[i] = window->line->latin1 [i]; + } + + cleanup_screen(&input); + } + + window->pending_read = 0; + window->line = NULL; + } +} + +static void clear_window(struct window *window) +{ + if(window->id == NULL) return; + + /* glk_window_clear() cannot be used while there are pending read requests. */ + cancel_read_events(window); + + glk_window_clear(window->id); + + window->x = window->y = 0; +} +#endif + +/* If restoring from an interrupt (which is a bad idea to begin with), + * it’s entirely possible that there will be pending read events that + * need to be canceled, so allow that. + */ +void cancel_all_events(void) +{ +#ifdef ZTERP_GLK + for(int i = 0; i < 8; i++) cancel_read_events(&windows[i]); +#endif +} + +static void resize_upper_window(long nlines) +{ +#ifdef ZTERP_GLK + if(upperwin->id == NULL) return; + + /* To avoid code duplication, put all window resizing code in + * update_delayed() and, if necessary, call it from here. + */ + delayed_window_shrink = nlines; + if(upper_window_height <= nlines || saw_input) update_delayed(); + + saw_input = 0; + + /* §8.6.1.1.2 */ + if(zversion == 3) clear_window(upperwin); + + /* As in a few other areas, changing the upper window causes reverse + * video to be deactivated, so reapply the current style. + */ + set_current_style(); +#endif +} + +void close_upper_window(void) +{ + /* The upper window is never destroyed; rather, when it’s closed, it + * shrinks to zero height. + */ + resize_upper_window(0); + +#ifdef ZTERP_GLK + delayed_window_shrink = -1; + saw_input = 0; +#endif + + set_current_window(mainwin); +} + +void get_screen_size(unsigned int *width, unsigned int *height) +{ + *width = 80; + *height = 24; + +#ifdef ZTERP_GLK + glui32 w, h; + + /* The main window can be proportional, and if so, its width is not + * generally useful because games tend to care about width with a + * fixed font. If a status window is available, or if an upper window + * is available, use that to calculate the width, because these + * windows will have a fixed-width font. The height is the combined + * height of all windows. + */ + glk_window_get_size(mainwin->id, &w, &h); + *height = h; + if(statuswin.id != NULL) + { + glk_window_get_size(statuswin.id, &w, &h); + *height += h; + } + if(upperwin->id != NULL) + { + glk_window_get_size(upperwin->id, &w, &h); + *height += h; + } + *width = w; +#else + zterp_os_get_screen_size(width, height); +#endif + + /* XGlk does not report the size of textbuffer windows, so here’s a safety net. */ + if(*width == 0) *width = 80; + if(*height == 0) *height = 24; + + /* Terrible hack: Because V6 is not properly supported, the window to + * which Journey writes its story is completely covered up by window + * 1. For the same reason, only the bottom 6 lines of window 1 are + * actually useful, even though the game expands it to cover the whole + * screen. By pretending that the screen height is only 6, the main + * window, where text is actually sent, becomes visible. + */ + if(is_story("83-890706") && *height > 6) *height = 6; +} + +#ifdef GLK_MODULE_LINE_TERMINATORS +static uint32_t *term_keys, term_size, term_nkeys; + +void term_keys_reset(void) +{ + free(term_keys); + term_keys = NULL; + term_size = 0; + term_nkeys = 0; +} + +static void insert_key(uint32_t key) +{ + if(term_nkeys == term_size) + { + term_size += 32; + + term_keys = realloc(term_keys, term_size * sizeof *term_keys); + if(term_keys == NULL) die("unable to allocate memory for terminating keys"); + } + + term_keys[term_nkeys++] = key; +} + +void term_keys_add(uint8_t key) +{ + switch(key) + { + case 129: insert_key(keycode_Up); break; + case 130: insert_key(keycode_Down); break; + case 131: insert_key(keycode_Left); break; + case 132: insert_key(keycode_Right); break; + case 133: insert_key(keycode_Func1); break; + case 134: insert_key(keycode_Func2); break; + case 135: insert_key(keycode_Func3); break; + case 136: insert_key(keycode_Func4); break; + case 137: insert_key(keycode_Func5); break; + case 138: insert_key(keycode_Func6); break; + case 139: insert_key(keycode_Func7); break; + case 140: insert_key(keycode_Func8); break; + case 141: insert_key(keycode_Func9); break; + case 142: insert_key(keycode_Func10); break; + case 143: insert_key(keycode_Func11); break; + case 144: insert_key(keycode_Func12); break; + + /* Keypad 0–9 should be here, but Glk doesn’t support that. */ + case 145: case 146: case 147: case 148: case 149: + case 150: case 151: case 152: case 153: case 154: + break; + + /* Mouse clicks would go here if I supported them. */ + case 252: case 253: case 254: + break; + + case 255: + for(int i = 129; i <= 144; i++) term_keys_add(i); + break; + + default: + ZASSERT(0, "invalid terminating key: %u", (unsigned)key); + break; + } +} +#endif + +/* Print out a character. The character is in “c” and is either Unicode + * or ZSCII; if the former, “unicode” is true. + */ +static void put_char_base(uint16_t c, int unicode) +{ + if(c == 0) return; + + if(streams & STREAM_MEMORY) + { + ZASSERT(stablei != -1, "invalid stream table"); + + /* When writing to memory, ZSCII should always be used (§7.5.3). */ + if(unicode) c = unicode_to_zscii_q[c]; + + user_store_byte(stables[stablei].table + stables[stablei].i++, c); + } + else + { + /* For screen and transcription, always prefer Unicode. */ + if(!unicode) c = zscii_to_unicode[c]; + + if(c != 0) + { + uint8_t zscii = 0; + + /* §16 makes no mention of what a newline in font 3 should map to. + * Other interpreters that implement font 3 assume it stays a + * newline, and this makes the most sense, so don’t do any + * translation in that case. + */ + if(curwin->font == FONT_CHARACTER && !options.disable_graphics_font && c != UNICODE_LINEFEED) + { + zscii = unicode_to_zscii[c]; + + /* These four characters have a “built-in” reverse video (see §16). */ + if(zscii >= 123 && zscii <= 126) + { + style_window->style ^= STYLE_REVERSE; + set_current_style(); + } + + c = zscii_to_font3[zscii]; + } +#ifdef ZTERP_GLK + if((streams & STREAM_SCREEN) && curwin->id != NULL) + { + cancel_read_events(curwin); + + if(curwin == upperwin) + { + /* Interpreters seem to have differing ideas about what + * happens when the cursor reaches the end of a line in the + * upper window. Some wrap, some let it run off the edge (or, + * at least, stop the text at the edge). The standard, from + * what I can see, says nothing on this issue. Follow Windows + * Frotz and don’t wrap. + */ + + if(c == UNICODE_LINEFEED) + { + if(upperwin->y < upper_window_height) + { + /* Glk wraps, so printing a newline when the cursor has + * already reached the edge of the screen will produce two + * newlines. + */ + if(upperwin->x < upper_window_width) GLK_PUT_CHAR(c); + + /* Even if a newline isn’t explicitly printed here + * (because the cursor is at the edge), setting + * upperwin->x to 0 will cause the next character to be on + * the next line because the text will have wrapped. + */ + upperwin->x = 0; + upperwin->y++; + } + } + else if(upperwin->x < upper_window_width && upperwin->y < upper_window_height) + { + upperwin->x++; + GLK_PUT_CHAR(c); + } + } + else + { + GLK_PUT_CHAR(c); + } + } +#else + if((streams & STREAM_SCREEN) && curwin == mainwin) zterp_io_putc(zterp_io_stdout(), c); +#endif + + /* If the reverse video bit was flipped (for the character font), flip it back. */ + if(zscii >= 123 && zscii <= 126) + { + style_window->style ^= STYLE_REVERSE; + set_current_style(); + } + + if((streams & STREAM_TRANS) && curwin == mainwin) zterp_io_putc(transio, c); + } + } +} + +void put_char_u(uint16_t c) +{ + put_char_base(c, 1); +} + +void put_char(uint8_t c) +{ + put_char_base(c, 0); +} + +static void put_string(const char *s) +{ + for(; *s != 0; s++) + { + if(*s == '\n') put_char(ZSCII_NEWLINE); + else put_char(*s); + } +} + +/* Decode and print a zcode string at address “addr”. This can be + * called recursively thanks to abbreviations; the initial call should + * have “in_abbr” set to 0. + * Each time a character is decoded, it is passed to the function + * “outc”. + */ +static int print_zcode(uint32_t addr, int in_abbr, void (*outc)(uint8_t)) +{ + int abbrev = 0, shift = 0, special = 0; + int c, lastc = 0; /* Initialize lastc to shut gcc up */ + uint16_t w; + uint32_t counter = addr; + int current_alphabet = 0; + + do + { + ZASSERT(counter < memory_size - 1, "string runs beyond the end of memory"); + + w = WORD(counter); + + for(int i = 10; i >= 0; i -= 5) + { + c = (w >> i) & 0x1f; + + if(special) + { + if(special == 2) lastc = c; + else outc((lastc << 5) | c); + + special--; + } + + else if(abbrev) + { + uint32_t new_addr; + + new_addr = user_word(header.abbr + 64 * (abbrev - 1) + 2 * c); + + /* new_addr is a word address, so multiply by 2 */ + print_zcode(new_addr * 2, 1, outc); + + abbrev = 0; + } + + else switch(c) + { + case 0: + outc(ZSCII_SPACE); + shift = 0; + break; + case 1: + if(zversion == 1) + { + outc(ZSCII_NEWLINE); + shift = 0; + break; + } + /* fallthrough */ + case 2: case 3: + if(zversion >= 3 || (zversion == 2 && c == 1)) + { + ZASSERT(!in_abbr, "abbreviation being used recursively"); + abbrev = c; + shift = 0; + } + else + { + shift = c - 1; + } + break; + case 4: case 5: + if(zversion <= 2) + { + current_alphabet = (current_alphabet + (c - 3)) % 3; + shift = 0; + } + else + { + shift = c - 3; + } + break; + case 6: + if(zversion <= 2) shift = (current_alphabet + shift) % 3; + + if(shift == 2) + { + shift = 0; + special = 2; + break; + } + /* fallthrough */ + default: + if(zversion <= 2 && c != 6) shift = (current_alphabet + shift) % 3; + + outc(atable[(26 * shift) + (c - 6)]); + shift = 0; + break; + } + } + + counter += 2; + } while((w & 0x8000) == 0); + + return counter - addr; +} + +/* Prints the string at addr “addr”. + * + * Returns the number of bytes the string took up. “outc” is passed as + * the character-print function to print_zcode(); if it is NULL, + * put_char is used. + */ +int print_handler(uint32_t addr, void (*outc)(uint8_t)) +{ + return print_zcode(addr, 0, outc != NULL ? outc : put_char); +} + +void zprint(void) +{ + pc += print_handler(pc, NULL); +} + +void zprint_ret(void) +{ + zprint(); + put_char(ZSCII_NEWLINE); + zrtrue(); +} + +void znew_line(void) +{ + put_char(ZSCII_NEWLINE); +} + +void zerase_window(void) +{ +#ifdef ZTERP_GLK + switch((int16_t)zargs[0]) + { + case -2: + for(int i = 0; i < 8; i++) clear_window(&windows[i]); + break; + case -1: + close_upper_window(); + /* fallthrough */ + case 0: + /* 8.7.3.2.1 says V5+ should have the cursor set to 1, 1 of the + * erased window; V4 the lower window goes bottom left, the upper + * to 1, 1. Glk doesn’t give control over the cursor when + * clearing, and that doesn’t really seem to be an issue; so just + * call glk_window_clear(). + */ + clear_window(mainwin); + break; + case 1: + clear_window(upperwin); + break; + default: + show_message("@erase_window: unhandled window: %d", (int16_t)zargs[0]); + break; + } + + /* glk_window_clear() kills reverse video in Gargoyle. Reapply style. */ + set_current_style(); +#endif +} + +void zerase_line(void) +{ +#ifdef ZTERP_GLK + /* XXX V6 does pixel handling here. */ + if(zargs[0] != 1 || curwin != upperwin || upperwin->id == NULL) return; + + for(long i = upperwin->x; i < upper_window_width; i++) GLK_PUT_CHAR(UNICODE_SPACE); + + glk_window_move_cursor(upperwin->id, upperwin->x, upperwin->y); +#endif +} + +/* XXX This is more complex in V6 and needs to be updated when V6 windowing is implemented. */ +static void set_cursor(uint16_t y, uint16_t x) +{ +#ifdef ZTERP_GLK + /* All the windows in V6 can have their cursor positioned; if V6 ever + * comes about this should be fixed. + */ + if(curwin != upperwin) return; + + /* -1 and -2 are V6 only, but at least Zracer passes -1 (or it’s + * trying to position the cursor to line 65535; unlikely!) + */ + if((int16_t)y == -1 || (int16_t)y == -2) return; + + /* §8.7.2.3 says 1,1 is the top-left, but at least one program (Paint + * and Corners) uses @set_cursor 0 0 to go to the top-left; so + * special-case it. + */ + if(y == 0) y = 1; + if(x == 0) x = 1; + + /* This is actually illegal, but some games (e.g. Beyond Zork) expect it to work. */ + if(y > upper_window_height) resize_upper_window(y); + + if(upperwin->id != NULL) + { + upperwin->x = x - 1; + upperwin->y = y - 1; + + glk_window_move_cursor(upperwin->id, x - 1, y - 1); + } +#endif +} + +void zset_cursor(void) +{ + set_cursor(zargs[0], zargs[1]); +} + +void zget_cursor(void) +{ +#ifdef ZTERP_GLK + user_store_word(zargs[0] + 0, upperwin->y + 1); + user_store_word(zargs[0] + 2, upperwin->x + 1); +#else + user_store_word(zargs[0] + 0, 1); + user_store_word(zargs[0] + 2, 1); +#endif +} + +#ifndef ZTERP_GLK +static int16_t fg_color = 1, bg_color = 1; +#elif defined(GARGLK) +static glui32 zcolor_map[] = { + zcolor_Default, + + 0x000000, /* Black */ + 0xef0000, /* Red */ + 0x00d600, /* Green */ + 0xefef00, /* Yellow */ + 0x006bb5, /* Blue */ + 0xff00ff, /* Magenta */ + 0x00efef, /* Cyan */ + 0xffffff, /* White */ + 0xb5b5b5, /* Light grey */ + 0x8c8c8c, /* Medium grey */ + 0x5a5a5a, /* Dark grey */ +}; +static glui32 fg_color = zcolor_Default, bg_color = zcolor_Default; + +void update_color(int which, unsigned long color) +{ + if(which < 2 || which > 12) return; + + zcolor_map[which - 1] = color; +} +#endif + +/* A window argument may be supplied in V6, and this needs to be implemented. */ +void zset_colour(void) +{ + /* Glk (apart from Gargoyle) has no color support. */ +#if !defined(ZTERP_GLK) || defined(GARGLK) + int16_t fg = zargs[0], bg = zargs[1]; + + /* In V6, each window has its own color settings. Since multiple + * windows are not supported, simply ignore all color requests except + * those in the main window. + */ + if(zversion == 6 && curwin != mainwin) return; + + if(options.disable_color) return; + + /* XXX -1 is a valid color in V6. */ +#ifdef GARGLK + if(fg >= 1 && fg <= (zversion >= 5 ? 12 : 9)) fg_color = zcolor_map[fg - 1]; + if(bg >= 1 && bg <= (zversion >= 5 ? 12 : 9)) bg_color = zcolor_map[bg - 1]; + +#else + if(fg >= 1 && fg <= 9) fg_color = fg; + if(bg >= 1 && bg <= 9) bg_color = bg; +#endif + + set_current_style(); +#endif +} + +#ifdef GARGLK +/* Convert a 15-bit color to a 24-bit color. */ +static glui32 convert_color(unsigned long color) +{ + /* Map 5-bit color values to 8-bit. */ + const uint8_t table[] = { + 0x00, 0x08, 0x10, 0x19, 0x21, 0x29, 0x31, 0x3a, + 0x42, 0x4a, 0x52, 0x5a, 0x63, 0x6b, 0x73, 0x7b, + 0x84, 0x8c, 0x94, 0x9c, 0xa5, 0xad, 0xb5, 0xbd, + 0xc5, 0xce, 0xd6, 0xde, 0xe6, 0xef, 0xf7, 0xff + }; + + return table[(color & 0x001f) >> 0] << 16 | + table[(color & 0x03e0) >> 5] << 8 | + table[(color & 0x7c00) >> 10] << 0; +} +#endif + +void zset_true_colour(void) +{ +#ifdef GARGLK + long fg = (int16_t)zargs[0], bg = (int16_t)zargs[1]; + + if (fg >= 0) fg_color = convert_color(fg); + else if(fg == -1) fg_color = zcolor_Default; + + if (bg >= 0) bg_color = convert_color(bg); + else if(bg == -1) bg_color = zcolor_Default; + + set_current_style(); +#endif +} + +int header_fixed_font; + +#ifdef GARGLK +/* Idea from Nitfol. */ +static const int style_map[] = +{ + style_Normal, + style_Normal, + + style_Subheader, /* Bold */ + style_Subheader, /* Bold */ + style_Emphasized, /* Italic */ + style_Emphasized, /* Italic */ + style_Alert, /* Bold Italic */ + style_Alert, /* Bold Italic */ + style_Preformatted, /* Fixed */ + style_Preformatted, /* Fixed */ + style_User1, /* Bold Fixed */ + style_User1, /* Bold Fixed */ + style_User2, /* Italic Fixed */ + style_User2, /* Italic Fixed */ + style_Note, /* Bold Italic Fixed */ + style_Note, /* Bold Italic Fixed */ +}; +#endif + +/* Yes, there are three ways to indicate that a fixed-width font should be used. */ +#define use_fixed_font() (header_fixed_font || curwin->font == FONT_FIXED || (style & STYLE_FIXED)) + +void set_current_style(void) +{ + unsigned style = style_window->style; +#ifdef ZTERP_GLK + if(curwin->id == NULL) return; + +#ifdef GARGLK + if(use_fixed_font()) style |= STYLE_FIXED; + + if(options.disable_fixed) style &= ~STYLE_FIXED; + + ZASSERT(style < 16, "invalid style selected: %x", (unsigned)style); + + glk_set_style(style_map[style]); + + garglk_set_reversevideo(style & STYLE_REVERSE); + + garglk_set_zcolors(fg_color, bg_color); +#else + /* Glk can’t mix other styles with fixed-width, but the upper window + * is always fixed, so if it is selected, there is no need to + * explicitly request it here. In addition, the user can disable + * fixed-width fonts or tell Bocfel to assume that the output font is + * already fixed (e.g. in an xterm); in either case, there is no need + * to request a fixed font. + * This means that another style can also be applied if applicable. + */ + if(use_fixed_font() && + !options.disable_fixed && + !options.assume_fixed && + curwin != upperwin) + { + glk_set_style(style_Preformatted); + return; + } + + /* According to standard 1.1, if mixed styles aren't available, the + * priority is Fixed, Italic, Bold, Reverse. + */ + if (style & STYLE_ITALIC) glk_set_style(style_Emphasized); + else if(style & STYLE_BOLD) glk_set_style(style_Subheader); + else if(style & STYLE_REVERSE) glk_set_style(style_Alert); + else glk_set_style(style_Normal); +#endif +#else + zterp_os_set_style(style, fg_color, bg_color); +#endif +} + +#undef use_fixed_font + +/* V6 has per-window styles, but all others have a global style; in this + * case, track styles via the main window. + */ +void zset_text_style(void) +{ + /* A style of 0 means all others go off. */ + if(zargs[0] == 0) style_window->style = STYLE_NONE; + else style_window->style |= zargs[0]; + + set_current_style(); +} + +/* Interpreters seem to disagree on @set_font. Given the code + + @set_font 4 -> i; + @set_font 1 -> j; + @set_font 0 -> k; + @set_font 1 -> l; + + * the following values are returned: + * Frotz 2.43: 0, 1, 1, 1 + * Gargoyle r384: 1, 4, 4, 4 + * Fizmo 0.6.5: 1, 4, 1, 0 + * Nitfol 0.5: 1, 4, 0, 1 + * Filfre .987: 1, 4, 0, 1 + * Zoom 1.1.4: 1, 1, 0, 1 + * ZLR 0.07: 0, 1, 0, 1 + * Windows Frotz 1.15: 1, 4, 1, 1 + * XZip 1.8.2: 0, 4, 0, 0 + * + * The standard says that “ID 0 means ‘the previous font’.” (§8.1.2). + * The Frotz 2.43 source code says that “zargs[0] = number of font or 0 + * to keep current font”. + * + * How to implement @set_font turns on the meaning of “previous”. Does + * it mean the previous font _after_ the @set_font call, meaning Frotz + * is right? Or is it the previous font _before_ the @set_font call, + * meaning the identity of two fonts needs to be tracked? + * + * Currently I do the latter. That yields the following: + * 1, 4, 1, 4 + * Almost comically, no interpreters agree with each other. + */ +void zset_font(void) +{ + struct window *win = curwin; + + if(zversion == 6 && znargs == 2 && (int16_t)zargs[1] != -3) + { + ZASSERT(zargs[1] < 8, "invalid window selected: %d", (int16_t)zargs[1]); + win = &windows[zargs[1]]; + } + + /* If no previous font has been stored, consider that an error. */ + if(zargs[0] == FONT_PREVIOUS && win->prev_font != FONT_NONE) + { + zargs[0] = win->prev_font; + zset_font(); + } + else if(zargs[0] == FONT_NORMAL || + (zargs[0] == FONT_CHARACTER && !options.disable_graphics_font) || + (zargs[0] == FONT_FIXED && !options.disable_fixed)) + { + store(win->font); + win->prev_font = win->font; + win->font = zargs[0]; + } + else + { + store(0); + } + + set_current_style(); +} + +void zprint_table(void) +{ + uint16_t text = zargs[0], width = zargs[1], height = zargs[2], skip = zargs[3]; + uint16_t n = 0; + +#ifdef ZTERP_GLK + uint16_t start = 0; /* initialize to appease gcc */ + + if(curwin == upperwin) start = upperwin->x + 1; +#endif + + if(znargs < 3) height = 1; + if(znargs < 4) skip = 0; + + for(uint16_t i = 0; i < height; i++) + { + for(uint16_t j = 0; j < width; j++) + { + put_char(user_byte(text + n++)); + } + + if(i + 1 != height) + { + n += skip; +#ifdef ZTERP_GLK + if(curwin == upperwin) + { + set_cursor(upperwin->y + 2, start); + } + else +#endif + { + put_char(ZSCII_NEWLINE); + } + } + } +} + +void zprint_char(void) +{ + /* Check 32 (space) first: a cursory examination of story files + * indicates that this is the most common value passed to @print_char. + * This appears to be due to V4+ games blanking the upper window. + */ +#define valid_zscii_output(c) ((c) == 32 || (c) == 0 || (c) == 9 || (c) == 11 || (c) == 13 || ((c) > 32 && (c) <= 126) || ((c) >= 155 && (c) <= 251)) + ZASSERT(valid_zscii_output(zargs[0]), "@print_char called with invalid character: %u", (unsigned)zargs[0]); +#undef valid_zscii_output + + put_char(zargs[0]); +} + +void zprint_num(void) +{ + char buf[7]; + int i = 0; + long v = (int16_t)zargs[0]; + + if(v < 0) v = -v; + + do + { + buf[i++] = '0' + (v % 10); + } while(v /= 10); + + if((int16_t)zargs[0] < 0) buf[i++] = '-'; + + while(i--) put_char(buf[i]); +} + +void zprint_addr(void) +{ + print_handler(zargs[0], NULL); +} + +void zprint_paddr(void) +{ + print_handler(unpack(zargs[0], 1), NULL); +} + +/* XXX This is more complex in V6 and needs to be updated when V6 windowing is implemented. */ +void zsplit_window(void) +{ + if(zargs[0] == 0) close_upper_window(); + else resize_upper_window(zargs[0]); +} + +void zset_window(void) +{ + set_current_window(find_window(zargs[0])); +} + +#ifdef ZTERP_GLK +static void window_change(void) +{ + /* When a textgrid (the upper window) in Gargoyle is rearranged, it + * forgets about reverse video settings, so reapply any styles to the + * current window (it doesn’t hurt if the window is a textbuffer). If + * the current window is not the upper window that’s OK, because + * set_current_style() is called when a @set_window is requested. + */ + set_current_style(); + + /* If the new window is smaller, the cursor of the upper window might + * be out of bounds. Pull it back in if so. + */ + if(zversion >= 3 && upperwin->id != NULL && upper_window_height > 0) + { + long x = upperwin->x, y = upperwin->y; + glui32 w, h; + + glk_window_get_size(upperwin->id, &w, &h); + + upper_window_width = w; + upper_window_height = h; + + if(x > w) x = w; + if(y > h) y = h; + + SWITCH_WINDOW_START(upperwin); + set_cursor(y + 1, x + 1); + SWITCH_WINDOW_END(); + } + + /* §8.4 + * Only 0x20 and 0x21 are mentioned; what of 0x22 and 0x24? Zoom and + * Windows Frotz both update the V5 header entries, so do that here, + * too. + * + * Also, no version restrictions are given, but assume V4+ per §11.1. + */ + if(zversion >= 4) + { + unsigned width, height; + + get_screen_size(&width, &height); + + STORE_BYTE(0x20, height > 254 ? 254 : height); + STORE_BYTE(0x21, width > 255 ? 255 : width); + + if(zversion >= 5) + { + STORE_WORD(0x22, width > UINT16_MAX ? UINT16_MAX : width); + STORE_WORD(0x24, height > UINT16_MAX ? UINT16_MAX : height); + } + } + else + { + zshow_status(); + } +} +#endif + +#ifdef ZTERP_GLK +static int timer_running; + +static void start_timer(uint16_t n) +{ + if(!TIMER_AVAILABLE()) return; + + if(timer_running) die("nested timers unsupported"); + glk_request_timer_events(n * 100); + timer_running = 1; +} + +static void stop_timer(void) +{ + if(!TIMER_AVAILABLE()) return; + + glk_request_timer_events(0); + timer_running = 0; +} + +static void request_char(void) +{ + if(have_unicode) glk_request_char_event_uni(curwin->id); + else glk_request_char_event(curwin->id); + + curwin->pending_read = 1; +} + +static void request_line(union line *line, glui32 maxlen, glui32 initlen) +{ + if(have_unicode) glk_request_line_event_uni(curwin->id, line->unicode, maxlen, initlen); + else glk_request_line_event(curwin->id, line->latin1, maxlen, initlen); + + curwin->pending_read = 1; + curwin->line = line; +} +#endif + +#define special_zscii(c) ((c) >= 129 && (c) <= 154) + +/* This is called when input stream 1 (read from file) is selected. If + * it succefully reads a character/line from the file, it fills the + * struct at “input” with the appropriate information and returns true. + * If it fails to read (likely due to EOF) then it sets the input stream + * back to the keyboard and returns false. + */ +static int istream_read_from_file(struct input *input) +{ + if(input->type == INPUT_CHAR) + { + long c; + + c = zterp_io_getc(istreamio); + if(c == -1) + { + input_stream(ISTREAM_KEYBOARD); + return 0; + } + + /* Don’t translate special ZSCII characters (cursor keys, function keys, keypad). */ + if(special_zscii(c)) input->key = c; + else input->key = unicode_to_zscii_q[c]; + } + else + { + long n; + uint16_t line[1024]; + + n = zterp_io_readline(istreamio, line, sizeof line / sizeof *line); + if(n == -1) + { + input_stream(ISTREAM_KEYBOARD); + return 0; + } + + if(n > input->maxlen) n = input->maxlen; + + input->len = n; + +#ifdef ZTERP_GLK + if(curwin->id != NULL) + { + glk_set_style(style_Input); + for(long i = 0; i < n; i++) GLK_PUT_CHAR(line[i]); + GLK_PUT_CHAR(UNICODE_LINEFEED); + set_current_style(); + } +#else + for(long i = 0; i < n; i++) zterp_io_putc(zterp_io_stdout(), line[i]); + zterp_io_putc(zterp_io_stdout(), UNICODE_LINEFEED); +#endif + + for(long i = 0; i < n; i++) input->line[i] = line[i]; + } + +#ifdef ZTERP_GLK + event_t ev; + + /* It’s possible that output is buffered, meaning that until + * glk_select() is called, output will not be displayed. When reading + * from a command-script, flush on each command so that output is + * visible while the script is being replayed. + */ + glk_select_poll(&ev); + switch(ev.type) + { + case evtype_None: + break; + case evtype_Arrange: + window_change(); + break; + default: + /* No other events should arrive. Timers are only started in + * get_input() and are stopped before that function returns. + * Input events will not happen with glk_select_poll(), and no + * other event type is expected to be raised. + */ + break; + } + + saw_input = 1; +#endif + + return 1; +} + +#ifdef GLK_MODULE_LINE_TERMINATORS +/* Glk returns terminating characters as keycode_*, but we need them as + * ZSCII. This should only ever be called with values that are matched + * in the switch, because those are the only ones that Glk was told are + * terminating characters. In the event that another keycode comes + * through, though, treat it as Enter. + */ +static uint8_t zscii_from_glk(glui32 key) +{ + switch(key) + { + case 13: return ZSCII_NEWLINE; + case keycode_Up: return 129; + case keycode_Down: return 130; + case keycode_Left: return 131; + case keycode_Right: return 131; + case keycode_Func1: return 133; + case keycode_Func2: return 134; + case keycode_Func3: return 135; + case keycode_Func4: return 136; + case keycode_Func5: return 137; + case keycode_Func6: return 138; + case keycode_Func7: return 139; + case keycode_Func8: return 140; + case keycode_Func9: return 141; + case keycode_Func10: return 142; + case keycode_Func11: return 143; + case keycode_Func12: return 144; + } + + return ZSCII_NEWLINE; +} +#endif + +#ifdef ZTERP_GLK +/* This is like strlen() but in addition to C strings it can find the + * length of a Unicode string (which is assumed to be zero terminated) + * if Unicode is being used. + */ +static size_t line_len(const union line *line) +{ + size_t i; + + if(!have_unicode) return strlen(line->latin1); + + for(i = 0; line->unicode[i] != 0; i++) + { + } + + return i; +} +#endif + +/* Attempt to read input from the user. The input type can be either a + * single character or a full line. If “timer” is not zero, a timer is + * started that fires off every “timer” tenths of a second (if the value + * is 1, it will timeout 10 times a second, etc.). Each time the timer + * times out the routine at address “routine” is called. If the routine + * returns true, the input is canceled. + * + * The function returns 1 if input was stored, 0 if there was a + * cancellation as described above. + */ +static int get_input(int16_t timer, int16_t routine, struct input *input) +{ + /* If either of these is zero, no timeout should happen. */ + if(timer == 0) routine = 0; + if(routine == 0) timer = 0; + + /* Flush all streams when input is requested. */ +#ifndef ZTERP_GLK + zterp_io_flush(zterp_io_stdout()); +#endif + zterp_io_flush(scriptio); + zterp_io_flush(transio); + + /* Generally speaking, newline will be the reason the line input + * stopped, so set it by default. It will be overridden where + * necessary. + */ + input->term = ZSCII_NEWLINE; + + if(istream == ISTREAM_FILE && istream_read_from_file(input)) return 1; +#ifdef ZTERP_GLK + int status = 0; + union line line; + struct window *saved = NULL; + + /* In V6, input might be requested on an unsupported window. If so, + * switch to the main window temporarily. + */ + if(curwin->id == NULL) + { + saved = curwin; + curwin = mainwin; + glk_set_window(curwin->id); + } + + if(input->type == INPUT_CHAR) + { + request_char(); + } + else + { + for(int i = 0; i < input->preloaded; i++) + { + if(have_unicode) line.unicode[i] = input->line[i]; + else line.latin1 [i] = input->line[i]; + } + + request_line(&line, input->maxlen, input->preloaded); + } + + if(timer != 0) start_timer(timer); + + while(status == 0) + { + event_t ev; + + glk_select(&ev); + + switch(ev.type) + { + case evtype_Arrange: + window_change(); + break; + + case evtype_Timer: + { + ZASSERT(timer != 0, "got unexpected evtype_Timer"); + + struct window *saved2 = curwin; + int ret; + + stop_timer(); + + ret = direct_call(routine); + + /* It’s possible for an interrupt to switch windows; if it + * does, simply switch back. This is the easiest way to deal + * with an undefined bit of the Z-machine. + */ + if(curwin != saved2) set_current_window(saved2); + + if(ret) + { + status = 2; + } + else + { + /* If this got reset to 0, that means an interrupt had to + * cancel the read event in order to either read or write. + */ + if(!curwin->pending_read) + { + if(input->type == INPUT_CHAR) request_char(); + else request_line(&line, input->maxlen, line_len(&line)); + } + + start_timer(timer); + } + } + + break; + + case evtype_CharInput: + ZASSERT(input->type == INPUT_CHAR, "got unexpected evtype_CharInput"); + ZASSERT(ev.win == curwin->id, "got evtype_CharInput on unexpected window"); + + status = 1; + + switch(ev.val1) + { + case keycode_Delete: input->key = 8; break; + case keycode_Return: input->key = 13; break; + case keycode_Escape: input->key = 27; break; + case keycode_Up: input->key = 129; break; + case keycode_Down: input->key = 130; break; + case keycode_Left: input->key = 131; break; + case keycode_Right: input->key = 132; break; + case keycode_Func1: input->key = 133; break; + case keycode_Func2: input->key = 134; break; + case keycode_Func3: input->key = 135; break; + case keycode_Func4: input->key = 136; break; + case keycode_Func5: input->key = 137; break; + case keycode_Func6: input->key = 138; break; + case keycode_Func7: input->key = 139; break; + case keycode_Func8: input->key = 140; break; + case keycode_Func9: input->key = 141; break; + case keycode_Func10: input->key = 142; break; + case keycode_Func11: input->key = 143; break; + case keycode_Func12: input->key = 144; break; + + default: + input->key = ZSCII_QUESTIONMARK; + + if(ev.val1 <= UINT16_MAX) + { + uint8_t c = unicode_to_zscii[ev.val1]; + + if(c != 0) input->key = c; + } + + break; + } + + break; + + case evtype_LineInput: + ZASSERT(input->type == INPUT_LINE, "got unexpected evtype_LineInput"); + ZASSERT(ev.win == curwin->id, "got evtype_LineInput on unexpected window"); + input->len = ev.val1; +#ifdef GLK_MODULE_LINE_TERMINATORS + if(zversion >= 5) input->term = zscii_from_glk(ev.val2); +#endif + status = 1; + break; + } + } + + stop_timer(); + + if(input->type == INPUT_CHAR) + { + glk_cancel_char_event(curwin->id); + } + else + { + /* On cancellation, the buffer still needs to be filled, because + * it’s possible that line input echoing has been turned off and the + * contents will need to be written out. + */ + if(status == 2) + { + event_t ev; + + glk_cancel_line_event(curwin->id, &ev); + input->len = ev.val1; + input->term = 0; + } + + for(glui32 i = 0; i < input->len; i++) + { + if(have_unicode) input->line[i] = line.unicode[i] > UINT16_MAX ? UNICODE_QUESTIONMARK : line.unicode[i]; + else input->line[i] = (uint8_t)line.latin1[i]; + } + } + + curwin->pending_read = 0; + curwin->line = NULL; + + if(status == 1) saw_input = 1; + + if(errorwin != NULL) + { + glk_window_close(errorwin, NULL); + errorwin = NULL; + } + + if(saved != NULL) + { + curwin = saved; + glk_set_window(curwin->id); + } + + return status != 2; +#else + if(input->type == INPUT_CHAR) + { + long n; + uint16_t line[64]; + + n = zterp_io_readline(zterp_io_stdin(), line, sizeof line / sizeof *line); + + /* On error/eof, or if an invalid key was typed, pretend “Enter” was hit. */ + if(n <= 0) + { + input->key = ZSCII_NEWLINE; + } + else + { + input->key = unicode_to_zscii[line[0]]; + if(input->key == 0) input->key = ZSCII_NEWLINE; + } + } + else + { + input->len = input->preloaded; + + if(input->maxlen > input->preloaded) + { + long n; + uint16_t line[1024]; + + n = zterp_io_readline(zterp_io_stdin(), line, sizeof line / sizeof *line); + if(n != -1) + { + if(n > input->maxlen - input->preloaded) n = input->maxlen - input->preloaded; + for(long i = 0; i < n; i++) input->line[i + input->preloaded] = line[i]; + input->len += n; + } + } + } + + return 1; +#endif +} + +void zread_char(void) +{ + uint16_t timer = 0; + uint16_t routine = zargs[2]; + struct input input = { .type = INPUT_CHAR }; + +#ifdef ZTERP_GLK + cancel_read_events(curwin); +#endif + + if(zversion >= 4 && znargs > 1) timer = zargs[1]; + + if(!get_input(timer, routine, &input)) + { + store(0); + return; + } + +#ifdef ZTERP_GLK + update_delayed(); +#endif + + if(streams & STREAM_SCRIPT) + { + /* Values 127 to 159 are not valid Unicode, and these just happen to + * match up to the values needed for special ZSCII keys, so store + * them as-is. + */ + if(special_zscii(input.key)) zterp_io_putc(scriptio, input.key); + else zterp_io_putc(scriptio, zscii_to_unicode[input.key]); + } + + store(input.key); +} + +#ifdef ZTERP_GLK +static void status_putc(uint8_t c) +{ + glk_put_char(zscii_to_unicode[c]); +} +#endif + +void zshow_status(void) +{ +#ifdef ZTERP_GLK + glui32 width, height; + char rhs[64]; + int first = variable(0x11), second = variable(0x12); + + if(statuswin.id == NULL) return; + + glk_window_clear(statuswin.id); + + SWITCH_WINDOW_START(&statuswin); + + glk_window_get_size(statuswin.id, &width, &height); + +#ifdef GARGLK + garglk_set_reversevideo(1); +#else + glk_set_style(style_Alert); +#endif + for(glui32 i = 0; i < width; i++) glk_put_char(ZSCII_SPACE); + + glk_window_move_cursor(statuswin.id, 1, 0); + + /* Variable 0x10 is global variable 1. */ + print_object(variable(0x10), status_putc); + + if(STATUS_IS_TIME()) + { + snprintf(rhs, sizeof rhs, "Time: %d:%02d%s ", (first + 11) % 12 + 1, second, first < 12 ? "am" : "pm"); + if(strlen(rhs) > width) snprintf(rhs, sizeof rhs, "%02d:%02d", first, second); + } + else + { + snprintf(rhs, sizeof rhs, "Score: %d Moves: %d ", first, second); + if(strlen(rhs) > width) snprintf(rhs, sizeof rhs, "%d/%d", first, second); + } + + if(strlen(rhs) <= width) + { + glk_window_move_cursor(statuswin.id, width - strlen(rhs), 0); + glk_put_string(rhs); + } + + SWITCH_WINDOW_END(); +#endif +} + +/* This is strcmp() except that the first string is Unicode. */ +static int unicmp(const uint32_t *s1, const char *s2) +{ + while(*s1 != 0 && *s2 == *s1) + { + s1++; + s2++; + } + + return *s1 - *s2; +} + +uint32_t read_pc; + +/* Try to parse a meta command. Returns true if input should be + * restarted, false to indicate no more input is required. In most + * cases input will be required because the game has requested it, but + * /undo and /restore jump to different locations, so the current @read + * no longer exists. + */ +static int handle_meta_command(const uint32_t *string) +{ + if(unicmp(string, "undo") == 0) + { + uint16_t flags2 = WORD(0x10); + int success = pop_save(); + + if(success != 0) + { + /* §6.1.2. */ + STORE_WORD(0x10, flags2); + + if(zversion >= 5) store(success); + else put_string("[undone]\n\n>"); + + return 0; + } + else + { + put_string("[no save found, unable to undo]"); + } + } + else if(unicmp(string, "scripton") == 0) + { + if(output_stream(OSTREAM_SCRIPT, 0)) put_string("[transcripting on]"); + else put_string("[transcripting failed]"); + } + else if(unicmp(string, "scriptoff") == 0) + { + output_stream(-OSTREAM_SCRIPT, 0); + put_string("[transcripting off]"); + } + else if(unicmp(string, "recon") == 0) + { + if(output_stream(OSTREAM_RECORD, 0)) put_string("[command recording on]"); + else put_string("[command recording failed]"); + } + else if(unicmp(string, "recoff") == 0) + { + output_stream(-OSTREAM_RECORD, 0); + put_string("[command recording off]"); + } + else if(unicmp(string, "replay") == 0) + { + if(input_stream(ISTREAM_FILE)) put_string("[replaying commands]"); + else put_string("[replaying commands failed]"); + } + else if(unicmp(string, "save") == 0) + { + if(interrupt_level() != 0) + { + put_string("[cannot call /save while in an interrupt]"); + } + else + { + uint32_t tmp = pc; + + /* pc is currently set to the next instruction, but the restore + * needs to come back to *this* instruction; so temporarily set + * pc back before saving. + */ + pc = read_pc; + if(do_save(1)) put_string("[saved]"); + else put_string("[save failed]"); + pc = tmp; + } + } + else if(unicmp(string, "restore") == 0) + { + if(do_restore(1)) + { + put_string("[restored]\n\n>"); + return 0; + } + else + { + put_string("[restore failed]"); + } + } + else if(unicmp(string, "help") == 0) + { + put_string( + "/undo: undo a turn\n" + "/scripton: start a transcript\n" + "/scriptoff: stop a transcript\n" + "/recon: start a command record\n" + "/recoff: stop a command record\n" + "/replay: replay a command record\n" + "/save: save the game\n" + "/restore: restore a game saved by /save" + ); + } + else + { + put_string("[unknown command]"); + } + + return 1; +} + +void zread(void) +{ + uint16_t text = zargs[0], parse = zargs[1]; + uint8_t maxchars = zversion >= 5 ? user_byte(text) : user_byte(text) - 1; + uint8_t zscii_string[maxchars]; + uint32_t string[maxchars + 1]; + struct input input = { .type = INPUT_LINE, .line = string, .maxlen = maxchars }; + uint16_t timer = 0; + uint16_t routine = zargs[3]; + +#ifdef ZTERP_GLK + cancel_read_events(curwin); +#endif + + if(zversion <= 3) zshow_status(); + + if(zversion >= 4 && znargs > 2) timer = zargs[2]; + + if(zversion >= 5) + { + int i; + + input.preloaded = user_byte(text + 1); + ZASSERT(input.preloaded <= maxchars, "too many preloaded characters: %d when max is %d", input.preloaded, maxchars); + + for(i = 0; i < input.preloaded; i++) string[i] = zscii_to_unicode[user_byte(text + i + 2)]; + string[i] = 0; + + /* Under garglk, preloaded input works as it’s supposed to. + * Under Glk, it can fail one of two ways: + * 1. The preloaded text is printed out once, but is not editable. + * 2. The preloaded text is printed out twice, the second being editable. + * I have chosen option #2. For non-Glk, option #1 is done by necessity. + */ +#ifdef GARGLK + if(curwin->id != NULL) garglk_unput_string_uni(string); +#endif + } + + if(!get_input(timer, routine, &input)) + { +#ifdef ZTERP_GLK + cleanup_screen(&input); +#endif + if(zversion >= 5) store(0); + return; + } + +#ifdef ZTERP_GLK + cleanup_screen(&input); +#endif + +#ifdef ZTERP_GLK + update_delayed(); +#endif + + if(options.enable_escape && (streams & STREAM_TRANS)) + { + zterp_io_putc(transio, 033); + zterp_io_putc(transio, '['); + for(int i = 0; options.escape_string[i] != 0; i++) zterp_io_putc(transio, options.escape_string[i]); + } + + for(int i = 0; i < input.len; i++) + { + zscii_string[i] = unicode_to_zscii_q[unicode_tolower(string[i])]; + if(streams & STREAM_TRANS) zterp_io_putc(transio, string[i]); + if(streams & STREAM_SCRIPT) zterp_io_putc(scriptio, string[i]); + } + + if(options.enable_escape && (streams & STREAM_TRANS)) + { + zterp_io_putc(transio, 033); + zterp_io_putc(transio, '['); + zterp_io_putc(transio, '0'); + zterp_io_putc(transio, 'm'); + } + + if(streams & STREAM_TRANS) zterp_io_putc(transio, UNICODE_LINEFEED); + if(streams & STREAM_SCRIPT) zterp_io_putc(scriptio, UNICODE_LINEFEED); + + if(!options.disable_meta_commands) + { + string[input.len] = 0; + + if(string[0] == '/') + { + if(handle_meta_command(string + 1)) + { + /* The game still wants input, so try again. */ + put_string("\n\n>"); + zread(); + } + + return; + } + + /* V1–4 do not have @save_undo, so simulate one each time @read is + * called. + * + * pc is currently set to the next instruction, but the undo needs + * to come back to *this* instruction; so temporarily set pc back + * before pushing the save. + */ + if(zversion <= 4) + { + uint32_t tmp_pc = pc; + + pc = read_pc; + push_save(); + pc = tmp_pc; + } + } + + if(zversion >= 5) + { + user_store_byte(text + 1, input.len); /* number of characters read */ + + for(int i = 0; i < input.len; i++) + { + user_store_byte(text + i + 2, zscii_string[i]); + } + + if(parse != 0) tokenize(text, parse, 0, 0); + + store(input.term); + } + else + { + for(int i = 0; i < input.len; i++) + { + user_store_byte(text + i + 1, zscii_string[i]); + } + + user_store_byte(text + input.len + 1, 0); + + tokenize(text, parse, 0, 0); + } +} + +void zprint_unicode(void) +{ + if(valid_unicode(zargs[0])) put_char_u(zargs[0]); + else put_char_u(UNICODE_QUESTIONMARK); +} + +void zcheck_unicode(void) +{ + uint16_t res = 0; + + /* valid_unicode() will tell which Unicode characters can be printed; + * and if the Unicode character is in the Unicode input table, it can + * also be read. If Unicode is not available, then any character >255 + * is invalid for both reading and writing. + */ + if(have_unicode || zargs[0] < 256) + { + if(valid_unicode(zargs[0])) res |= 0x01; + if(unicode_to_zscii[zargs[0]] != 0) res |= 0x02; + } + + store(res); +} + +/* Should picture_data and get_wind_prop be moved to a V6 source file? */ +void zpicture_data(void) +{ + if(zargs[0] == 0) + { + user_store_word(zargs[1] + 0, 0); + user_store_word(zargs[1] + 2, 0); + } + + /* No pictures means no valid pictures, so never branch. */ + branch_if(0); +} + +void zget_wind_prop(void) +{ + uint16_t val; + struct window *win; + + win = find_window(zargs[0]); + + /* These are mostly bald-faced lies. */ + switch(zargs[1]) + { + case 0: /* y coordinate */ + val = 0; + break; + case 1: /* x coordinate */ + val = 0; + break; + case 2: /* y size */ + val = 100; + break; + case 3: /* x size */ + val = 100; + break; + case 4: /* y cursor */ + val = 0; + break; + case 5: /* x cursor */ + val = 0; + break; + case 6: /* left margin size */ + val = 0; + break; + case 7: /* right margin size */ + val = 0; + break; + case 8: /* newline interrupt routine */ + val = 0; + break; + case 9: /* interrupt countdown */ + val = 0; + break; + case 10: /* text style */ + val = win->style; + break; + case 11: /* colour data */ + val = (9 << 8) | 2; + break; + case 12: /* font number */ + val = win->font; + break; + case 13: /* font size */ + val = (10 << 8) | 10; + break; + case 14: /* attributes */ + val = 0; + break; + case 15: /* line count */ + val = 0; + break; + case 16: /* true foreground colour */ + val = 0; + break; + case 17: /* true background colour */ + val = 0; + break; + default: + die("unknown window property: %u", (unsigned)zargs[1]); + } + + store(val); +} + +/* This is not correct, because @output_stream does not work as it + * should with a width argument; however, this does print out the + * contents of a table that was sent to stream 3, so it’s at least + * somewhat useful. + * + * Output should be to the currently-selected window, but since V6 is + * only marginally supported, other windows are not active. Send to the + * main window for the time being. + */ +void zprint_form(void) +{ + SWITCH_WINDOW_START(mainwin); + + for(uint16_t i = 0; i < user_word(zargs[0]); i++) + { + put_char(user_byte(zargs[0] + 2 + i)); + } + + put_char(ZSCII_NEWLINE); + + SWITCH_WINDOW_END(); +} + +void zmake_menu(void) +{ + branch_if(0); +} + +void zbuffer_screen(void) +{ + store(0); +} + +#ifdef GARGLK +/* Glk does not guarantee great control over how various styles are + * going to look, but Gargoyle does. Abusing the Glk “style hints” + * functions allows for quite fine-grained control over style + * appearance. First, clear the (important) attributes for each style, + * and then recreate each in whatever mold is necessary. Re-use some + * that are expected to be correct (emphasized for italic, subheader for + * bold, and so on). + */ +static void set_default_styles(void) +{ + int styles[] = { style_Subheader, style_Emphasized, style_Alert, style_Preformatted, style_User1, style_User2, style_Note }; + + for(int i = 0; i < 7; i++) + { + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Size, 0); + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Weight, 0); + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Oblique, 0); + + /* This sets wintype_TextGrid to be proportional, which of course is + * wrong; but text grids are required to be fixed, so Gargoyle + * simply ignores this hint for those windows. + */ + glk_stylehint_set(wintype_AllTypes, styles[i], stylehint_Proportional, 1); + } +} +#endif + +int create_mainwin(void) +{ +#ifdef ZTERP_GLK + +#ifdef GARGLK + set_default_styles(); + + /* Bold */ + glk_stylehint_set(wintype_AllTypes, style_Subheader, stylehint_Weight, 1); + + /* Italic */ + glk_stylehint_set(wintype_AllTypes, style_Emphasized, stylehint_Oblique, 1); + + /* Bold Italic */ + glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Weight, 1); + glk_stylehint_set(wintype_AllTypes, style_Alert, stylehint_Oblique, 1); + + /* Fixed */ + glk_stylehint_set(wintype_AllTypes, style_Preformatted, stylehint_Proportional, 0); + + /* Bold Fixed */ + glk_stylehint_set(wintype_AllTypes, style_User1, stylehint_Weight, 1); + glk_stylehint_set(wintype_AllTypes, style_User1, stylehint_Proportional, 0); + + /* Italic Fixed */ + glk_stylehint_set(wintype_AllTypes, style_User2, stylehint_Oblique, 1); + glk_stylehint_set(wintype_AllTypes, style_User2, stylehint_Proportional, 0); + + /* Bold Italic Fixed */ + glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Weight, 1); + glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Oblique, 1); + glk_stylehint_set(wintype_AllTypes, style_Note, stylehint_Proportional, 0); +#endif + + mainwin->id = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); + if(mainwin->id == NULL) return 0; + glk_set_window(mainwin->id); + +#ifdef GLK_MODULE_LINE_ECHO + mainwin->has_echo = glk_gestalt(gestalt_LineInputEcho, 0); + if(mainwin->has_echo) glk_set_echo_line_event(mainwin->id, 0); +#endif + + return 1; +#else + return 1; +#endif +} + +int create_statuswin(void) +{ +#ifdef ZTERP_GLK + if(statuswin.id == NULL) statuswin.id = glk_window_open(mainwin->id, winmethod_Above | winmethod_Fixed, 1, wintype_TextGrid, 0); + return statuswin.id != NULL; +#else + return 0; +#endif +} + +int create_upperwin(void) +{ +#ifdef ZTERP_GLK + /* On a restart, this function will get called again. It would be + * possible to try to resize the upper window to 0 if it already + * exists, but it’s easier to just destroy and recreate it. + */ + if(upperwin->id != NULL) glk_window_close(upperwin->id, NULL); + + /* The upper window appeared in V3. */ + if(zversion >= 3) + { + upperwin->id = glk_window_open(mainwin->id, winmethod_Above | winmethod_Fixed, 0, wintype_TextGrid, 0); + upperwin->x = upperwin->y = 0; + upper_window_height = 0; + + if(upperwin->id != NULL) + { + glui32 w, h; + + glk_window_get_size(upperwin->id, &w, &h); + upper_window_width = w; + + if(h != 0 || upper_window_width == 0) + { + glk_window_close(upperwin->id, NULL); + upperwin->id = NULL; + } + } + } + + return upperwin->id != NULL; +#else + return 0; +#endif +} + +void init_screen(void) +{ + for(int i = 0; i < 8; i++) + { + windows[i].style = STYLE_NONE; + windows[i].font = FONT_NORMAL; + windows[i].prev_font = FONT_NONE; + +#ifdef ZTERP_GLK + clear_window(&windows[i]); +#ifdef GLK_MODULE_LINE_TERMINATORS + if(windows[i].id != NULL && term_nkeys != 0 && glk_gestalt(gestalt_LineTerminators, 0)) glk_set_terminators_line_event(windows[i].id, term_keys, term_nkeys); +#endif +#endif + } + + close_upper_window(); + +#ifdef ZTERP_GLK + if(statuswin.id != NULL) glk_window_clear(statuswin.id); + + if(errorwin != NULL) + { + glk_window_close(errorwin, NULL); + errorwin = NULL; + } + + stop_timer(); + +#ifdef GARGLK + fg_color = zcolor_Default; + bg_color = zcolor_Default; +#endif + +#else + fg_color = 1; + bg_color = 1; +#endif + + if(scriptio != NULL) zterp_io_close(scriptio); + scriptio = NULL; + + input_stream(ISTREAM_KEYBOARD); + + streams = STREAM_SCREEN; + stablei = -1; + set_current_window(mainwin); +} diff --git a/interpreters/bocfel/screen.h b/interpreters/bocfel/screen.h new file mode 100644 index 0000000..4e11101 --- /dev/null +++ b/interpreters/bocfel/screen.h @@ -0,0 +1,92 @@ +#ifndef ZTERP_SCREEN_H +#define ZTERP_SCREEN_H + +#include + +#ifdef ZTERP_GLK +#include +#endif + +/* Boolean flag describing whether the header bit meaning “fixed font” is set. */ +extern int header_fixed_font; + +extern uint32_t read_pc; + +void init_screen(void); + +int create_mainwin(void); +int create_statuswin(void); +int create_upperwin(void); +void get_screen_size(unsigned int *, unsigned int *); +void close_upper_window(void); +void cancel_all_events(void); + +/* Text styles. */ +#define STYLE_NONE (0U ) +#define STYLE_REVERSE (1U << 0) +#define STYLE_BOLD (1U << 1) +#define STYLE_ITALIC (1U << 2) +#define STYLE_FIXED (1U << 3) + +void show_message(const char *, ...); + +#ifdef GLK_MODULE_LINE_TERMINATORS +void term_keys_reset(void); +void term_keys_add(uint8_t); +#endif + +#ifdef GARGLK +void update_color(int, unsigned long); +#endif + +/* Output streams. */ +#define OSTREAM_SCREEN 1 +#define OSTREAM_SCRIPT 2 +#define OSTREAM_MEMORY 3 +#define OSTREAM_RECORD 4 + +/* Input streams. */ +#define ISTREAM_KEYBOARD 0 +#define ISTREAM_FILE 1 + +int output_stream(int16_t, uint16_t); +int input_stream(int); + +void set_current_style(void); + +int print_handler(uint32_t, void (*)(uint8_t)); +void put_char_u(uint16_t); +void put_char(uint8_t); + +void zoutput_stream(void); +void zinput_stream(void); +void zprint(void); +void zprint_ret(void); +void znew_line(void); +void zerase_window(void); +void zerase_line(void); +void zset_cursor(void); +void zget_cursor(void); +void zset_colour(void); +void zset_true_colour(void); +void zset_text_style(void); +void zset_font(void); +void zprint_table(void); +void zprint_char(void); +void zprint_num(void); +void zprint_addr(void); +void zprint_paddr(void); +void zsplit_window(void); +void zset_window(void); +void zread_char(void); +void zshow_status(void); +void zread(void); +void zprint_unicode(void); +void zcheck_unicode(void); +void zpicture_data(void); +void zget_wind_prop(void); +void zprint_form(void); +void zmake_menu(void); +void zbuffer_screen(void); + +#endif diff --git a/interpreters/bocfel/stack.c b/interpreters/bocfel/stack.c new file mode 100644 index 0000000..493dd36 --- /dev/null +++ b/interpreters/bocfel/stack.c @@ -0,0 +1,1077 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "stack.h" +#include "branch.h" +#include "iff.h" +#include "io.h" +#include "memory.h" +#include "process.h" +#include "screen.h" +#include "util.h" +#include "zterp.h" + +struct call_frame +{ + uint32_t pc; + uint16_t *sp; + uint8_t nlocals; + uint8_t nargs; + uint16_t where; + uint16_t locals[15]; +}; + +static struct call_frame *frames; +static struct call_frame *fp; + +#define BASE_OF_FRAMES frames +static struct call_frame *TOP_OF_FRAMES; +#define CURRENT_FRAME (fp - 1) +#define NFRAMES ((long)(fp - frames)) + +static uint16_t *stack; +static uint16_t *sp; + +#define BASE_OF_STACK stack +static uint16_t *TOP_OF_STACK; + +static void PUSH_STACK(uint16_t n) { ZASSERT(sp != TOP_OF_STACK, "stack overflow"); *sp++ = n; } +static uint16_t POP_STACK(void) { ZASSERT(sp > CURRENT_FRAME->sp, "stack underflow"); return *--sp; } + +static struct save_state +{ + uint32_t pc; + + uint32_t memsize; + uint8_t *memory; + + uint32_t stack_size; + uint16_t *stack; + + long nframes; + struct call_frame *frames; + + struct save_state *prev, *next; +} *saves_head, *saves_tail; + +static long nsaves; + +static void add_frame(uint32_t pc_, uint16_t *sp_, uint8_t nlocals, uint8_t nargs, uint16_t where) +{ + ZASSERT(fp != TOP_OF_FRAMES, "call stack too deep: %ld", NFRAMES + 1); + + fp->pc = pc_; + fp->sp = sp_; + fp->nlocals = nlocals; + fp->nargs = nargs; + fp->where = where; + + fp++; +} + +void init_stack(void) +{ + /* Allocate space for the evaluation and call stacks. + * Clamp the size between 1 and the largest value that will not + * produce an overflow of size_t when multiplied by the size of the + * type. + * Also, the call stack can be no larger than 0xffff so that the + * result of a @catch will fit into a 16-bit integer. + */ + if(stack == NULL) + { +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define CLAMP(n, a, b) ((n) < (a) ? (a) : (n) > (b) ? (b): (n)) + options.eval_stack_size = CLAMP(options.eval_stack_size, 1, SIZE_MAX / sizeof *stack); + stack = malloc(options.eval_stack_size * sizeof *stack); + if(stack == NULL) die("unable to allocate %lu bytes for the evaluation stack", options.eval_stack_size * (unsigned long)sizeof *stack); + TOP_OF_STACK = &stack[options.eval_stack_size]; + + options.call_stack_size = CLAMP(options.call_stack_size, 1, MIN(0xffff, (SIZE_MAX / sizeof *frames) - sizeof *frames)); + /* One extra to help with saving (thus the subtraction of sizeof *frames above). */ + frames = malloc((options.call_stack_size + 1) * sizeof *frames); + if(frames == NULL) die("unable to allocate %lu bytes for the call stack", (options.call_stack_size + 1) * (unsigned long)sizeof *frames); + TOP_OF_FRAMES = &frames[options.call_stack_size]; +#undef MIN +#undef CLAMP + } + + sp = BASE_OF_STACK; + fp = BASE_OF_FRAMES; + + /* Quetzal requires a dummy frame in non-V6 games, so do that here. */ + if(zversion != 6) add_frame(0, sp, 0, 0, 0); + + /* Free all previous save states (from @save_undo). */ + while(saves_head != NULL) + { + struct save_state *tmp = saves_head; + saves_head = saves_head->next; + free(tmp->stack); + free(tmp->frames); + free(tmp->memory); + free(tmp); + } + saves_tail = NULL; + nsaves = 0; +} + +uint16_t variable(uint16_t var) +{ + ZASSERT(var < 0x100, "unable to decode variable %u", (unsigned)var); + + /* Stack */ + if(var == 0) + { + return POP_STACK(); + } + + /* Locals */ + else if(var <= 0x0f) + { + ZASSERT(var <= CURRENT_FRAME->nlocals, "attempting to read from nonexistent local variable %d: routine has %d", (int)var, CURRENT_FRAME->nlocals); + return CURRENT_FRAME->locals[var - 1]; + } + + /* Globals */ + else if(var <= 0xff) + { + var -= 0x10; + return WORD(header.globals + (var * 2)); + } + + /* This is an “impossible” situation (ie, the game did something wrong). + * It will be caught above if safety checks are turned on, but if they + * are not, do what we can: lie. + */ + return -1; +} + +void store_variable(uint16_t var, uint16_t n) +{ + ZASSERT(var < 0x100, "unable to decode variable %u", (unsigned)var); + + /* Stack. */ + if(var == 0) + { + PUSH_STACK(n); + } + + /* Local variables. */ + else if(var <= 0x0f) + { + ZASSERT(var <= CURRENT_FRAME->nlocals, "attempting to store to nonexistent local variable %d: routine has %d", (int)var, CURRENT_FRAME->nlocals); + CURRENT_FRAME->locals[var - 1] = n; + } + + /* Global variables. */ + else if(var <= 0xff) + { + var -= 0x10; + STORE_WORD(header.globals + (var * 2), n); + } +} + +uint16_t *stack_top_element(void) +{ + ZASSERT(sp > CURRENT_FRAME->sp, "stack underflow"); + + return sp - 1; +} + +void zpush(void) +{ + PUSH_STACK(zargs[0]); +} + +void zpull(void) +{ + uint16_t v; + + if(zversion != 6) + { + v = POP_STACK(); + + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) *stack_top_element() = v; + else store_variable(zargs[0], v); + } + else + { + if(znargs == 0) + { + v = POP_STACK(); + } + else + { + uint16_t slots = user_word(zargs[0]) + 1; + + v = user_word(zargs[0] + (2 * slots)); + + user_store_word(zargs[0], slots); + } + + store(v); + } +} + +void zload(void) +{ + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) store(*stack_top_element()); + else store(variable(zargs[0])); +} + +void zstore(void) +{ + /* The z-spec 1.1 requires indirect variable references to the stack not to push/pop */ + if(zargs[0] == 0) *stack_top_element() = zargs[1]; + else store_variable(zargs[0], zargs[1]); +} + +void call(int do_store) +{ + uint32_t jmp_to; + uint8_t nlocals; + uint16_t where; + + if(zargs[0] == 0) + { + /* call(2) should never happen if zargs[0] is 0. */ + if(do_store) store(0); + return; + } + + jmp_to = unpack(zargs[0], 0); + ZASSERT(jmp_to < memory_size - 1, "call to invalid address 0x%lx", (unsigned long)jmp_to); + + nlocals = BYTE(jmp_to++); + ZASSERT(nlocals <= 15, "too many (%d) locals at 0x%lx", nlocals, (unsigned long)jmp_to - 1); + + if(zversion <= 4) ZASSERT(jmp_to + (nlocals * 2) < memory_size, "call to invalid address 0x%lx", (unsigned long)jmp_to); + + switch(do_store) + { + case 1: where = BYTE(pc++); break; /* Where to store return value */ + case 0: where = 0xff + 1; break; /* Or a tag meaning no return value */ + default: where = 0xff + 2; break; /* Or a tag meaning push the return value */ + } + + add_frame(pc, sp, nlocals, znargs - 1, where); + + for(int i = 0; i < nlocals; i++) + { + if(i < znargs - 1) + { + CURRENT_FRAME->locals[i] = zargs[i + 1]; + } + else + { + if(zversion <= 4) CURRENT_FRAME->locals[i] = WORD(jmp_to + (2 * i)); + else CURRENT_FRAME->locals[i] = 0; + } + } + + /* Take care of locals! */ + if(zversion <= 4) jmp_to += nlocals * 2; + + pc = jmp_to; +} + +#ifdef ZTERP_GLK +uint16_t direct_call(uint16_t routine) +{ + uint16_t saved_args[znargs]; + uint16_t saved_nargs; + + memcpy(saved_args, zargs, sizeof saved_args); + saved_nargs = znargs; + + znargs = 1; + zargs[0] = routine; + call(2); + + process_instructions(); + + memcpy(zargs, saved_args, sizeof saved_args); + znargs = saved_nargs; + + return POP_STACK(); +} +#endif + +void zcall_store(void) +{ + call(1); +} +void zcall_nostore(void) +{ + call(0); +} + +void do_return(uint16_t retval) +{ + uint16_t where; + + ZASSERT(NFRAMES > 1, "return attempted outside of a function"); + + pc = CURRENT_FRAME->pc; + sp = CURRENT_FRAME->sp; + where = CURRENT_FRAME->where; + fp--; + + if(where <= 0xff) + { + store_variable(where, retval); + } + else if(where == 0xff + 2) + { + PUSH_STACK(retval); + break_from(interrupt_level()); + } +} + +void zret_popped(void) +{ + do_return(POP_STACK()); +} + +void zpop(void) +{ + POP_STACK(); +} + +void zcatch(void) +{ + ZASSERT(zversion == 6 || NFRAMES > 1, "@catch called outside of a function"); + + /* Must account for the dummy frame in non-V6 stories. */ + store(zversion == 6 ? NFRAMES : NFRAMES - 1); +} + +void zthrow(void) +{ + /* As with @catch, account for the dummy frame. */ + if(zversion != 6) zargs[1]++; + + ZASSERT(zversion == 6 || NFRAMES > 1, "@throw called outside of a function"); + ZASSERT(zargs[1] <= NFRAMES, "unwinding too far"); + + fp = BASE_OF_FRAMES + zargs[1]; + + do_return(zargs[0]); +} + +void zret(void) +{ + do_return(zargs[0]); +} + +void zrtrue(void) +{ + do_return(1); +} + +void zrfalse(void) +{ + do_return(0); +} + +void zcheck_arg_count(void) +{ + ZASSERT(zversion == 6 || NFRAMES > 1, "@check_arg_count called outside of a function"); + + branch_if(zargs[0] <= CURRENT_FRAME->nargs); +} + +void zpop_stack(void) +{ + if(znargs == 1) + { + for(uint16_t i = 0; i < zargs[0]; i++) POP_STACK(); + } + else + { + user_store_word(zargs[1], user_word(zargs[1]) + zargs[0]); + } +} + +void zpush_stack(void) +{ + uint16_t slots = user_word(zargs[1]); + + if(slots == 0) + { + branch_if(0); + return; + } + + user_store_word(zargs[1] + (2 * slots), zargs[0]); + user_store_word(zargs[1], slots - 1); + + branch_if(1); +} + +/* Compress dynamic memory according to Quetzal. Memory is allocated + * for the passed-in pointer, and must be freed by the caller. The + * return value is the size of compressed memory, or 0 on failure. + */ +static uint32_t compress_memory(uint8_t **compressed) +{ + uint32_t ret = 0; + long i = 0; + uint8_t *tmp; + + /* The output buffer needs to be 1.5× the size of dynamic memory for + * the worst-case scenario: every other byte in memory differs from + * the story file. This will cause every other byte to take up two + * bytes in the output, thus creating 3 bytes of output for every 2 of + * input. This should round up for the extreme case of alternating + * zero/non-zero bytes with zeroes at the beginning and end, but due + * to the fact that trailing zeroes are not stored, it does not need + * to. + */ + tmp = malloc((3 * header.static_start) / 2); + if(tmp == NULL) return 0; + + while(1) + { + long run = i; + + /* Count zeroes. Stop counting when: + * • The end of dynamic memory is reached + * • A non-zero value is found + */ + while(i < header.static_start && (BYTE(i) ^ dynamic_memory[i]) == 0) + { + i++; + } + + run = i - run; + + /* A run of zeroes at the end need not be written. */ + if(i == header.static_start) break; + + /* If there has been a run of zeroes, write them out + * 256 at a time. + */ + while(run > 0) + { + tmp[ret++] = 0; + tmp[ret++] = (run > 256 ? 255 : run - 1); + run -= 256; + } + + /* The current byte differs from the story, so write it. */ + tmp[ret++] = BYTE(i) ^ dynamic_memory[i]; + + i++; + } + + *compressed = realloc(tmp, ret); + if(*compressed == NULL) *compressed = tmp; + + return ret; +} + +/* Reverse of the above function. */ +static int uncompress_memory(const uint8_t *compressed, uint32_t size) +{ + uint32_t memory_index = 0; + + memcpy(memory, dynamic_memory, header.static_start); + + for(uint32_t i = 0; i < size; i++) + { + if(compressed[i] != 0) + { + if(memory_index == header.static_start) return -1; + STORE_BYTE(memory_index, BYTE(memory_index) ^ compressed[i]); + memory_index++; + } + else + { + if(++i == size) return -1; + + if(memory_index + (compressed[i] + 1) > header.static_start) return -1; + memory_index += (compressed[i] + 1); + } + } + + return 0; +} + +/* Push the current game state onto the game-state stack. */ +int push_save(void) +{ + struct save_state *new; + + if(options.max_saves == 0) return -1; + + new = malloc(sizeof *new); + if(new == NULL) goto err; + new->stack = NULL; + new->frames = NULL; + + new->pc = pc; + + new->stack_size = sp - BASE_OF_STACK; + new->stack = malloc(new->stack_size * sizeof *new->stack); + if(new->stack == NULL) goto err; + memcpy(new->stack, BASE_OF_STACK, new->stack_size * sizeof *new->stack); + + new->nframes = NFRAMES; + new->frames = malloc(new->nframes * sizeof *new->frames); + if(new->frames == NULL) goto err; + memcpy(new->frames, BASE_OF_FRAMES, new->nframes * sizeof *new->frames); + + if(options.disable_undo_compression) + { + new->memory = malloc(header.static_start); + if(new->memory == NULL) goto err; + memcpy(new->memory, memory, header.static_start); + } + else + { + new->memsize = compress_memory(&new->memory); + if(new->memsize == 0) goto err; + } + + /* If the maximum number has been reached, drop the last element. + * A negative value for max_saves means there is no maximum. + */ + if(options.max_saves > 0 && nsaves == options.max_saves) + { + struct save_state *tmp = saves_tail; + saves_tail = saves_tail->prev; + if(saves_tail == NULL) saves_head = NULL; + else saves_tail->next = NULL; + free(tmp->stack); + free(tmp->frames); + free(tmp->memory); + free(tmp); + nsaves--; + } + + new->next = saves_head; + new->prev = NULL; + if(new->next != NULL) new->next->prev = new; + saves_head = new; + if(saves_tail == NULL) saves_tail = new; + + nsaves++; + + return 1; + +err: + if(new != NULL) + { + free(new->stack); + free(new->frames); + free(new); + } + + return 0; +} + +/* Pop the last-stored game state and jump to it. */ +int pop_save(void) +{ + struct save_state *p; + + if(nsaves == 0) return 0; + + p = saves_head; + + pc = p->pc; + + if(options.disable_undo_compression) + { + memcpy(memory, p->memory, header.static_start); + } + else + { + /* If this fails it’s a bug: unlike Quetzal files, the contents of + * p->memory are known to be good, because the compression was done + * by us with no chance for corruption (apart, again, from bugs). + */ + if(uncompress_memory(p->memory, p->memsize) == -1) die("error uncompressing memory: unable to continue"); + } + + sp = BASE_OF_STACK + p->stack_size; + memcpy(BASE_OF_STACK, p->stack, sizeof *sp * p->stack_size); + + fp = BASE_OF_FRAMES + p->nframes; + memcpy(BASE_OF_FRAMES, p->frames, sizeof *p->frames * p->nframes); + + /* Never pop off the last state. A story has every right to call + * @restore_undo as many times as it called @save_undo. However, if + * there aren’t enough save slots, popping off the last state would + * cause @restore_undo to return failure when it should not. + */ + if(nsaves > 1) + { + saves_head = saves_head->next; + saves_head->prev = NULL; + + free(p->stack); + free(p->frames); + free(p->memory); + free(p); + + nsaves--; + } + + return 2; +} + +void zsave_undo(void) +{ + if(interrupt_level() != 0) die("@save_undo called inside of an interrupt"); + + store(push_save()); +} + +void zrestore_undo(void) +{ + uint16_t flags2; + + /* §6.1.2: Flags 2 should be preserved. */ + flags2 = WORD(0x10); + store(pop_save()); + STORE_WORD(0x10, flags2); +} + +/* Quetzal save/restore functions. */ +static jmp_buf exception; +#define WRITE8(v) do { uint8_t v_ = (v); if(zterp_io_write(savefile, &v_, sizeof v_) != sizeof v_) longjmp(exception, 1); local_written += 1; } while(0) +#define WRITE16(v) do { uint16_t w_ = (v); WRITE8(w_ >> 8); WRITE8(w_ & 0xff); } while(0) +#define WRITE32(v) do { uint32_t x_ = (v); WRITE8(x_ >> 24); WRITE8((x_ >> 16) & 0xff); WRITE8((x_ >> 8) & 0xff); WRITE8(x_ & 0xff); } while(0) +#define WRITEID(v) WRITE32(STRID(v)) + +static size_t quetzal_write_stack(zterp_io *savefile) +{ + size_t local_written = 0; + + /* Add one more “fake” call frame with just enough information to + * calculate the evaluation stack used by the current routine. + */ + fp->sp = sp; + for(struct call_frame *p = BASE_OF_FRAMES; p != fp; p++) + { + uint8_t temp; + + WRITE8((p->pc >> 16) & 0xff); + WRITE8((p->pc >> 8) & 0xff); + WRITE8((p->pc >> 0) & 0xff); + + temp = p->nlocals; + if(p->where > 0xff) temp |= 0x10; + WRITE8(temp); + + if(p->where > 0xff) WRITE8(0); + else WRITE8(p->where); + + WRITE8((1U << p->nargs) - 1); + + /* number of words of evaluation stack used */ + WRITE16((p + 1)->sp - p->sp); + + /* local variables */ + for(int i = 0; i < p->nlocals; i++) WRITE16(p->locals[i]); + + /* evaluation stack */ + for(ptrdiff_t i = 0; i < (p + 1)->sp - p->sp; i++) WRITE16(p->sp[i]); + } + + return local_written; +} + +int save_quetzal(zterp_io *savefile, int is_meta) +{ + if(setjmp(exception) != 0) return 0; + + size_t local_written = 0; + size_t game_len; + uint32_t memsize; + uint8_t *compressed; + uint8_t *mem = memory; + long stks_pos; + size_t stack_size; + + WRITEID("FORM"); + WRITEID(" "); /* to be filled in */ + WRITEID(is_meta ? "BFMS" : "IFZS"); + + WRITEID("IFhd"); + WRITE32(13); + WRITE16(header.release); + zterp_io_write(savefile, header.serial, 6); + local_written += 6; + WRITE16(header.checksum); + WRITE8(pc >> 16); + WRITE8(pc >> 8); + WRITE8(pc & 0xff); + WRITE8(0); /* padding */ + + /* Store the filename in an IntD chunk. */ + game_len = 12 + strlen(game_file); + WRITEID("IntD"); + WRITE32(game_len); + WRITEID("UNIX"); + WRITE8(0x02); + WRITE8(0); + WRITE16(0); + WRITEID(" "); + zterp_io_write(savefile, game_file, game_len - 12); + local_written += (game_len - 12); + if(game_len & 1) WRITE8(0); + + memsize = compress_memory(&compressed); + + /* It is possible for the compressed memory size to be larger than + * uncompressed; in this case, just store the uncompressed memory. + */ + if(memsize > 0 && memsize < header.static_start) + { + mem = compressed; + WRITEID("CMem"); + } + else + { + memsize = header.static_start; + WRITEID("UMem"); + } + WRITE32(memsize); + zterp_io_write(savefile, mem, memsize); + local_written += memsize; + if(memsize & 1) WRITE8(0); /* padding */ + free(compressed); + + WRITEID("Stks"); + stks_pos = zterp_io_tell(savefile); + WRITEID(" "); /* to be filled in */ + stack_size = quetzal_write_stack(savefile); + local_written += stack_size; + if(stack_size & 1) WRITE8(0); /* padding */ + + zterp_io_seek(savefile, 4, SEEK_SET); + WRITE32(local_written - 8); /* entire file size minus 8 (FORM + size) */ + + zterp_io_seek(savefile, stks_pos, SEEK_SET); + WRITE32(stack_size); /* size of the stacks chunk */ + + return 1; +} + +/* Restoring can put the system in an inconsistent state by restoring + * only part of memory: the save file may be corrupt and cause failure + * part way through updating memory, for example. This set of functions + * takes a snapshot of the current state of dynamic memory and the + * stacks so they can be restored on failure. + */ +static uint8_t *memory_backup; +static uint16_t *stack_backup; +static int stack_backup_size; +static struct call_frame *frames_backup; +static int frames_backup_size; + +static void memory_snapshot_free(void) +{ + free(memory_backup); + free(stack_backup); + free(frames_backup); + + memory_backup = NULL; + stack_backup = NULL; + frames_backup = NULL; +} + +static void memory_snapshot(void) +{ + memory_snapshot_free(); + + memory_backup = malloc(header.static_start); + if(memory_backup == NULL) goto err; + + memcpy(memory_backup, memory, header.static_start); + + stack_backup_size = sp - stack; + if(stack_backup_size != 0) + { + stack_backup = malloc(stack_backup_size * sizeof *stack); + if(stack_backup == NULL) goto err; + memcpy(stack_backup, stack, stack_backup_size * sizeof *stack); + } + + frames_backup_size = fp - frames; + if(frames_backup_size != 0) + { + frames_backup = malloc(frames_backup_size * sizeof *frames); + if(frames_backup == NULL) goto err; + memcpy(frames_backup, frames, frames_backup_size * sizeof *frames); + } + + return; + +err: + memory_snapshot_free(); + + return; +} + +static int memory_restore(void) +{ + /* stack_backup and frames_backup will be NULL if the stacks were + * empty, so use memory_backup to determine if a snapshot has been + * taken. + */ + if(memory_backup == NULL) return 0; + + memcpy(memory, memory_backup, header.static_start); + if(stack_backup != NULL) memcpy(stack, stack_backup, stack_backup_size * sizeof *stack); + sp = stack + stack_backup_size; + if(frames_backup != NULL) memcpy(frames, frames_backup, frames_backup_size * sizeof *frames); + fp = frames + frames_backup_size; + + memory_snapshot_free(); + + return 1; +} + +#define goto_err(...) do { show_message("save file error: " __VA_ARGS__); goto err; } while(0) +#define goto_death(...) do { show_message("save file error: " __VA_ARGS__); goto death; } while(0) + +int restore_quetzal(zterp_io *savefile, int is_meta) +{ + zterp_iff *iff; + uint32_t size; + uint32_t n = 0; + uint8_t ifhd[13]; + + iff = zterp_iff_parse(savefile, is_meta ? "BFMS" : "IFZS"); + + if(iff == NULL || + !zterp_iff_find(iff, "IFhd", &size) || + size != 13 || + zterp_io_read(savefile, ifhd, sizeof ifhd) != sizeof ifhd) + { + goto_err("corrupted save file or not a save file at all"); + } + + if(((ifhd[0] << 8) | ifhd[1]) != header.release || + memcmp(&ifhd[2], header.serial, sizeof header.serial) != 0 || + ((ifhd[8] << 8) | ifhd[9]) != header.checksum) + { + goto_err("wrong game or version"); + } + + memory_snapshot(); + + if(zterp_iff_find(iff, "CMem", &size)) + { + uint8_t buf[size]; /* Too big for the stack? */ + + if(zterp_io_read(savefile, buf, size) != size) goto_err("unexpected eof reading compressed memory"); + + if(uncompress_memory(buf, size) == -1) goto_death("memory cannot be uncompressed"); + } + else if(zterp_iff_find(iff, "UMem", &size)) + { + if(size != header.static_start) goto_err("memory size mismatch"); + if(zterp_io_read(savefile, memory, header.static_start) != header.static_start) goto_death("unexpected eof reading memory"); + } + else + { + goto_err("no memory chunk found"); + } + + if(!zterp_iff_find(iff, "Stks", &size)) goto_death("no stacks chunk found"); + + sp = BASE_OF_STACK; + fp = BASE_OF_FRAMES; + + while(n < size) + { + uint8_t frame[8]; + uint8_t nlocals; + uint16_t nstack; + uint8_t nargs = 0; + + if(zterp_io_read(savefile, frame, sizeof frame) != sizeof frame) goto_death("unexpected eof reading stack frame"); + n += sizeof frame; + + nlocals = frame[3] & 0xf; + nstack = (frame[6] << 8) | frame[7]; + frame[5]++; + while(frame[5] >>= 1) nargs++; + + add_frame((frame[0] << 16) | (frame[1] << 8) | frame[2], sp, nlocals, nargs, (frame[3] & 0x10) ? 0xff + 1 : frame[4]); + + for(int i = 0; i < nlocals; i++) + { + uint16_t l; + + if(!zterp_io_read16(savefile, &l)) goto_death("unexpected eof reading local variable"); + CURRENT_FRAME->locals[i] = l; + + n += sizeof l; + } + + for(uint16_t i = 0; i < nstack; i++) + { + uint16_t s; + + if(!zterp_io_read16(savefile, &s)) goto_death("unexpected eof reading stack entry"); + PUSH_STACK(s); + + n += sizeof s; + } + } + + if(n != size) goto_death("stack size mismatch"); + + zterp_iff_free(iff); + memory_snapshot_free(); + + pc = (ifhd[10] << 16) | (ifhd[11] << 8) | ifhd[12]; + + return 1; + +death: + /* At this point, something vital (memory and/or the stacks) has been + * scribbed upon; if there was a successful backup, restore it. + * Otherwise the only course of action is to exit. + */ + if(!memory_restore()) die("the system is likely in an inconsistent state"); + +err: + /* A snapshot may have been taken, but neither memory nor the stacks + * have been overwritten, so just free the snapshot. + */ + memory_snapshot_free(); + zterp_iff_free(iff); + return 0; +} + +#undef goto_err +#undef goto_death + +/* Perform all aspects of a save, apart from storing/branching. + * Returns true if the save was success, false if not. + * “is_meta” is true if this save file is from a meta-save. + */ +int do_save(int is_meta) +{ + zterp_io *savefile; + int success; + + savefile = zterp_io_open(NULL, ZTERP_IO_WRONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + warning("unable to open save file"); + return 0; + } + + success = save_quetzal(savefile, is_meta); + + zterp_io_close(savefile); + + return success; +} + +/* The suggested filename is ignored, because Glk and, at least as of + * right now, zterp_io_open(), do not provide a method to do this. + * The “prompt” argument added by standard 1.1 is thus also ignored. + */ +void zsave(void) +{ + if(interrupt_level() != 0) die("@save called inside of an interrupt"); + + int success = do_save(0); + + if(zversion <= 3) branch_if(success); + else store(success); +} + +/* Perform all aspects of a restore, apart from storing/branching. + * Returns true if the restore was success, false if not. + * “is_meta” is true if this save file is expected to be from a + * meta-save. + */ +int do_restore(int is_meta) +{ + zterp_io *savefile; + uint16_t flags2; + int success; + + savefile = zterp_io_open(NULL, ZTERP_IO_RDONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + warning("unable to open save file"); + return 0; + } + + flags2 = WORD(0x10); + + success = restore_quetzal(savefile, is_meta); + + zterp_io_close(savefile); + + if(success) + { + /* On a successful restore, we are outside of any interrupt (since + * @save cannot be called inside an interrupt), so reset the level + * back to zero. In addition, there may be pending read events that + * need to be canceled, so do that, too. + */ + reset_level(); + cancel_all_events(); + + /* §8.6.1.3 */ + if(zversion == 3) close_upper_window(); + + /* The save might be from a different interpreter with different + * capabilities, so update the header to indicate what the current + * capabilities are... + */ + write_header(); + + /* ...except that flags2 should be preserved (§6.1.2). */ + STORE_WORD(0x10, flags2); + + /* Redraw the status line in games that use one. */ + if(zversion <= 3) zshow_status(); + } + + return success; +} + +void zrestore(void) +{ + int success = do_restore(0); + + if(zversion <= 3) branch_if(success); + else store(success ? 2 : 0); +} diff --git a/interpreters/bocfel/stack.h b/interpreters/bocfel/stack.h new file mode 100644 index 0000000..ab87413 --- /dev/null +++ b/interpreters/bocfel/stack.h @@ -0,0 +1,63 @@ +#ifndef ZTERP_STACK_H +#define ZTERP_STACK_H + +#include + +#include "io.h" + +#define DEFAULT_STACK_SIZE 0x4000 +#define DEFAULT_CALL_DEPTH 0x400 + +void init_stack(void); + +uint16_t variable(uint16_t); +void store_variable(uint16_t, uint16_t); +uint16_t *stack_top_element(void); + +void call(int); +#ifdef ZTERP_GLK +uint16_t direct_call(uint16_t); +#endif +void do_return(uint16_t); + +int save_quetzal(zterp_io *, int); +int restore_quetzal(zterp_io *, int); + +int do_save(int); +int do_restore(int); + +int push_save(void); +int pop_save(void); + +void zpush(void); +void zpull(void); +void zload(void); +void zstore(void); +void zret_popped(void); +void zpop(void); +void zcatch(void); +void zthrow(void); +void zret(void); +void zrtrue(void); +void zrfalse(void); +void zcheck_arg_count(void); +void zpop_stack(void); +void zpush_stack(void); +void zsave_undo(void); +void zrestore_undo(void); +void zsave(void); +void zrestore(void); + +void zcall_store(void); +void zcall_nostore(void); + +#define zcall zcall_store +#define zcall_1n zcall_nostore +#define zcall_1s zcall_store +#define zcall_2n zcall_nostore +#define zcall_2s zcall_store +#define zcall_vn zcall_nostore +#define zcall_vn2 zcall_nostore +#define zcall_vs2 zcall_store + +#endif diff --git a/interpreters/bocfel/table.c b/interpreters/bocfel/table.c new file mode 100644 index 0000000..1de5e9e --- /dev/null +++ b/interpreters/bocfel/table.c @@ -0,0 +1,90 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "table.h" +#include "branch.h" +#include "memory.h" +#include "process.h" +#include "zterp.h" + +void zcopy_table(void) +{ + uint16_t first = zargs[0], second = zargs[1], size = zargs[2]; + + if(second == 0) + { + for(uint16_t i = 0; i < size; i++) user_store_byte(first + i, 0); + } + else if( (first > second) || (int16_t)size < 0 ) + { + long n = labs((int16_t)size); + for(long i = 0; i < n; i++) user_store_byte(second + i, user_byte(first + i)); + } + else + { + for(uint16_t i = 0; i < size; i++) user_store_byte(second + size - i - 1, user_byte(first + size - i - 1)); + } +} + +void zscan_table(void) +{ + uint16_t addr = zargs[1]; + + if(znargs < 4) zargs[3] = 0x82; + + for(uint16_t i = 0; i < zargs[2]; i++) + { + if( + ( (zargs[3] & 0x80) && (user_word(addr) == zargs[0])) || + (!(zargs[3] & 0x80) && (user_byte(addr) == zargs[0])) + ) + { + store(addr); + branch_if(1); + return; + } + + addr += zargs[3] & 0x7f; + } + + store(0); + branch_if(0); +} + +void zloadw(void) +{ + store(user_word(zargs[0] + (2 * zargs[1]))); +} + +void zloadb(void) +{ + store(user_byte(zargs[0] + zargs[1])); +} + +void zstoreb(void) +{ + user_store_byte(zargs[0] + zargs[1], zargs[2]); +} + +void zstorew(void) +{ + user_store_word(zargs[0] + (2 * zargs[1]), zargs[2]); +} diff --git a/interpreters/bocfel/table.h b/interpreters/bocfel/table.h new file mode 100644 index 0000000..0855541 --- /dev/null +++ b/interpreters/bocfel/table.h @@ -0,0 +1,11 @@ +#ifndef ZTERP_TABLE_H +#define ZTERP_TABLE_H + +void zcopy_table(void); +void zscan_table(void); +void zloadw(void); +void zloadb(void); +void zstoreb(void); +void zstorew(void); + +#endif diff --git a/interpreters/bocfel/unicode.c b/interpreters/bocfel/unicode.c new file mode 100644 index 0000000..78f610d --- /dev/null +++ b/interpreters/bocfel/unicode.c @@ -0,0 +1,357 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "unicode.h" +#include "memory.h" +#include "util.h" +#include "zterp.h" + +int have_unicode; + +/* + * The index is the ZSCII value, minus 155 (so entry 0 refers to ZSCII + * value 155); and the value at the index is the Unicode character that + * the ZSCII value maps to. Because Latin-1 and Unicode are equivalent + * for 0–255, this table maps to both Unicode and Latin1, with the + * caveat that values greater than 255 should be considered invalid in + * Latin-1, and are translated as a question mark below in + * setup_tables() where appropriate. + */ +#define UNICODE_TABLE_SIZE 97 +static int unicode_entries = 69; +static uint16_t unicode_table[UNICODE_TABLE_SIZE] = { +0x00e4, 0x00f6, 0x00fc, 0x00c4, 0x00d6, 0x00dc, 0x00df, 0x00bb, 0x00ab, +0x00eb, 0x00ef, 0x00ff, 0x00cb, 0x00cf, 0x00e1, 0x00e9, 0x00ed, 0x00f3, +0x00fa, 0x00fd, 0x00c1, 0x00c9, 0x00cd, 0x00d3, 0x00da, 0x00dd, 0x00e0, +0x00e8, 0x00ec, 0x00f2, 0x00f9, 0x00c0, 0x00c8, 0x00cc, 0x00d2, 0x00d9, +0x00e2, 0x00ea, 0x00ee, 0x00f4, 0x00fb, 0x00c2, 0x00ca, 0x00ce, 0x00d4, +0x00db, 0x00e5, 0x00c5, 0x00f8, 0x00d8, 0x00e3, 0x00f1, 0x00f5, 0x00c3, +0x00d1, 0x00d5, 0x00e6, 0x00c6, 0x00e7, 0x00c7, 0x00fe, 0x00f0, 0x00de, +0x00d0, 0x00a3, 0x0153, 0x0152, 0x00a1, 0x00bf, +}; + +/* If a non-default Unicode table is found, this function is called with + * its address; it updates the Unicode table above. + */ +void parse_unicode_table(uint16_t utable) +{ + if(utable >= memory_size) die("corrupted story: unicode table out of range"); + + unicode_entries = BYTE(utable++); + + if(unicode_entries > UNICODE_TABLE_SIZE) die("corrupted story: too many entries in the unicode table"); + if(utable + (2 * unicode_entries) >= memory_size) die("corrupted story: unicode table out of range"); + + for(int i = 0; i < unicode_entries; i++) + { + unicode_table[i] = WORD(utable + (2 * i)); + } +} + +/* Table used to convert a ZSCII value to Unicode; and since this is + * only used for output, non-output values will be returned as a + * question mark. + */ +uint16_t zscii_to_unicode[UINT8_MAX + 1]; + +/* These tables translate a Unicode or (Latin-1) character into its + * ZSCII equivalent. Only valid Unicode characters are translated (that + * is, those in the range 32–126, or 160 and above). + * + * The first table will translate invalid Unicode characters to zero; + * the second, to a question mark. + */ +uint8_t unicode_to_zscii [UINT16_MAX + 1]; +uint8_t unicode_to_zscii_q[UINT16_MAX + 1]; + +/* Convenience table: pass through all values 0–255, but yield a question mark + * for others. */ +uint8_t unicode_to_latin1[UINT16_MAX + 1]; + +/* Convert ZSCII to Unicode line-drawing/rune characters. */ +uint16_t zscii_to_font3[UINT8_MAX + 1]; + +/* Lookup table to see if a character is in the alphabet table. Key is + * the character, value is the index in the alphabet table, or -1. + */ +int atable_pos[UINT8_MAX + 1]; + +/* Not all fonts provide all characters, so there + * may well be a lot of question marks. + */ +static void build_font3_table(void) +{ + for(int i = 0; i < UINT8_MAX; i++) zscii_to_font3[i] = UNICODE_QUESTIONMARK; + + zscii_to_font3[ 32] = UNICODE_SPACE; + zscii_to_font3[ 33] = 0x2190; /* ← */ + zscii_to_font3[ 34] = 0x2192; /* → */ + zscii_to_font3[ 35] = 0x2571; /* ╱ */ + zscii_to_font3[ 36] = 0x2572; /* ╲ */ + zscii_to_font3[ 37] = UNICODE_SPACE; + zscii_to_font3[ 38] = 0x2500; /* ─ */ + zscii_to_font3[ 39] = 0x2500; /* ─ */ + zscii_to_font3[ 40] = 0x2502; /* │ */ + zscii_to_font3[ 41] = 0x2502; /* │ (this should be slightly offset, but whatever) */ + zscii_to_font3[ 42] = 0x2534; /* ┴ */ + zscii_to_font3[ 43] = 0x252c; /* ┬ */ + zscii_to_font3[ 44] = 0x251c; /* ├ */ + zscii_to_font3[ 45] = 0x2524; /* ┤ */ + zscii_to_font3[ 46] = 0x2514; /* └ */ + zscii_to_font3[ 47] = 0x250c; /* ┌ */ + zscii_to_font3[ 48] = 0x2510; /* ┐ */ + zscii_to_font3[ 49] = 0x2518; /* ┘ */ + + /* There are a few characters that have no box-drawing equivalents. + * These are the pieces that have connections sticking out of them, + * used to link rooms together. There are two options: have filled + * boxes with no connections which makes the rooms look nice but the + * connections look bad, or unfilled boxes with connections which + * results in bad looking rooms but attached connections. The end + * result is something like this: + * + * No connections: Connections: + * ╲ ╱ ╲ ╱ + * ┌─┐ ▗▄▖ ╲─┐ ▗▄╱ + * │ ├─▐█▌─ │ ├─┤█├─ + * └─┘ ▝▀▘ └─┘ ▝┬▘ + * │ │ + * + * By default the former is done, but the latter can be chosen. + */ + zscii_to_font3[ 50] = options.enable_alt_graphics ? 0x2571 : 0x2514; /* ╱ or └ */ + zscii_to_font3[ 51] = options.enable_alt_graphics ? 0x2572 : 0x250c; /* ╲ or ┌ */ + zscii_to_font3[ 52] = options.enable_alt_graphics ? 0x2571 : 0x2510; /* ╱ or ┐ */ + zscii_to_font3[ 53] = options.enable_alt_graphics ? 0x2572 : 0x2518; /* ╲ or ┘ */ + + zscii_to_font3[ 54] = 0x2588; /* █ */ + zscii_to_font3[ 56] = 0x2584; /* ▄ */ + zscii_to_font3[ 55] = 0x2580; /* ▀ */ + zscii_to_font3[ 57] = 0x258c; /* ▌ */ + zscii_to_font3[ 58] = 0x2590; /* ▐ */ + + zscii_to_font3[ 59] = options.enable_alt_graphics ? 0x2534 : 0x2584; /* ┴ or ▄ */ + zscii_to_font3[ 60] = options.enable_alt_graphics ? 0x252c : 0x2580; /* ┬ or ▀ */ + zscii_to_font3[ 61] = options.enable_alt_graphics ? 0x251c : 0x258c; /* ├ or ▌ */ + zscii_to_font3[ 62] = options.enable_alt_graphics ? 0x2524 : 0x2590; /* ┤ or ▐ */ + + zscii_to_font3[ 63] = 0x259d; /* ▝ */ + zscii_to_font3[ 64] = 0x2597; /* ▗ */ + zscii_to_font3[ 65] = 0x2596; /* ▖ */ + zscii_to_font3[ 66] = 0x2598; /* ▘ */ + + zscii_to_font3[ 67] = options.enable_alt_graphics ? 0x2571 : 0x259d; /* ╱ or ▝ */ + zscii_to_font3[ 68] = options.enable_alt_graphics ? 0x2572 : 0x2597; /* ╲ or ▗ */ + zscii_to_font3[ 69] = options.enable_alt_graphics ? 0x2571 : 0x2596; /* ╱ or ▖ */ + zscii_to_font3[ 70] = options.enable_alt_graphics ? 0x2572 : 0x2598; /* ╲ or ▘ */ + + zscii_to_font3[ 75] = 0x2594; /* ▔ */ + zscii_to_font3[ 76] = 0x2581; /* ▁ */ + zscii_to_font3[ 77] = 0x258f; /* ▏ */ + zscii_to_font3[ 78] = 0x2595; /* ▕ */ + + zscii_to_font3[ 79] = UNICODE_SPACE; + zscii_to_font3[ 80] = 0x258f; /* ▏ */ + zscii_to_font3[ 81] = 0x258e; /* ▎ */ + zscii_to_font3[ 82] = 0x258d; /* ▍ */ + zscii_to_font3[ 83] = 0x258c; /* ▌ */ + zscii_to_font3[ 84] = 0x258b; /* ▋ */ + zscii_to_font3[ 85] = 0x258a; /* ▊ */ + zscii_to_font3[ 86] = 0x2589; /* ▉ */ + zscii_to_font3[ 87] = 0x2588; /* █ */ + zscii_to_font3[ 88] = 0x2595; /* ▕ */ + zscii_to_font3[ 89] = 0x258f; /* ▏ */ + + zscii_to_font3[ 90] = 0x2573; /* ╳ */ + zscii_to_font3[ 91] = 0x253c; /* ┼ */ + zscii_to_font3[ 92] = 0x2191; /* ↑ */ + zscii_to_font3[ 93] = 0x2193; /* ↓ */ + zscii_to_font3[ 94] = 0x2195; /* ↕ */ + zscii_to_font3[ 95] = 0x2b1c; /* ⬜ */ + zscii_to_font3[ 96] = UNICODE_QUESTIONMARK; + zscii_to_font3[ 97] = 0x16aa; /* ᚪ */ + zscii_to_font3[ 98] = 0x16d2; /* ᛒ */ + zscii_to_font3[ 99] = 0x16c7; /* ᛇ */ + zscii_to_font3[100] = 0x16de; /* ᛞ */ + zscii_to_font3[101] = 0x16d6; /* ᛖ */ + zscii_to_font3[102] = 0x16a0; /* ᚠ */ + zscii_to_font3[103] = 0x16b7; /* ᚷ */ + zscii_to_font3[104] = 0x16bb; /* ᚻ */ + zscii_to_font3[105] = 0x16c1; /* ᛁ */ + zscii_to_font3[106] = 0x16e8; /* ᛨ */ + zscii_to_font3[107] = 0x16e6; /* ᛦ */ + zscii_to_font3[108] = 0x16da; /* ᛚ */ + zscii_to_font3[109] = 0x16d7; /* ᛗ */ + zscii_to_font3[110] = 0x16be; /* ᚾ */ + zscii_to_font3[111] = 0x16a9; /* ᚩ */ + zscii_to_font3[112] = UNICODE_QUESTIONMARK; /* no good symbol */ + zscii_to_font3[113] = 0x0068; /* Unicode 'h'; close to the rune. */ + zscii_to_font3[114] = 0x16b1; /* ᚱ */ + zscii_to_font3[115] = 0x16cb; /* ᛋ */ + zscii_to_font3[116] = 0x16cf; /* ᛏ */ + zscii_to_font3[117] = 0x16a2; /* ᚢ */ + zscii_to_font3[118] = 0x16e0; /* ᛠ */ + zscii_to_font3[119] = 0x16b9; /* ᚹ */ + zscii_to_font3[120] = 0x16c9; /* ᛉ */ + zscii_to_font3[121] = 0x16a5; /* ᚥ */ + zscii_to_font3[122] = 0x16df; /* ᛟ */ + + /* These are reversed (see §16); a slightly ugly hack in screen.c is + * used to accomplish this. + */ + zscii_to_font3[123] = 0x2191; /* ↑ */ + zscii_to_font3[124] = 0x2193; /* ↓ */ + zscii_to_font3[125] = 0x2195; /* ↕ */ + zscii_to_font3[126] = UNICODE_QUESTIONMARK; +} + +void setup_tables(void) +{ + /*** ZSCII to Unicode table. ***/ + + for(int i = 0; i < UINT8_MAX + 1; i++) zscii_to_unicode[i] = UNICODE_QUESTIONMARK; + zscii_to_unicode[0] = 0; + zscii_to_unicode[ZSCII_NEWLINE] = UNICODE_LINEFEED; + + if(zversion == 6) zscii_to_unicode[ 9] = UNICODE_SPACE; /* Tab. */ + if(zversion == 6) zscii_to_unicode[11] = UNICODE_SPACE; /* Sentence space. */ + + for(int i = 32; i < 127; i++) zscii_to_unicode[i] = i; + for(int i = 0; i < unicode_entries; i++) + { + uint16_t c = unicode_table[i]; + + if(!valid_unicode(c)) c = UNICODE_QUESTIONMARK; + + /* If Unicode is not available, then any values > 255 are invalid. */ + else if(!have_unicode && c > 255) c = UNICODE_QUESTIONMARK; + + zscii_to_unicode[i + 155] = c; + } + + /*** Unicode to ZSCII tables. ***/ + + /* Default values. */ + memset(unicode_to_zscii, 0, sizeof unicode_to_zscii); + memset(unicode_to_zscii_q, ZSCII_QUESTIONMARK, sizeof unicode_to_zscii_q); + + /* First fill up the entries found in the Unicode table. */ + for(int i = 0; i < unicode_entries; i++) + { + uint16_t c = unicode_table[i]; + + if(valid_unicode(c)) + { + unicode_to_zscii [c] = i + 155; + unicode_to_zscii_q[c] = i + 155; + } + } + + /* Now the values that are equivalent in ZSCII and Unicode. */ + for(int i = 32; i < 127; i++) + { + unicode_to_zscii [i] = i; + unicode_to_zscii_q[i] = i; + } + + /* Properly translate a newline. */ + unicode_to_zscii_q[UNICODE_LINEFEED] = ZSCII_NEWLINE; + + /*** Unicode to Latin1 table. ***/ + + memset(unicode_to_latin1, UNICODE_QUESTIONMARK, sizeof unicode_to_latin1); + for(int i = 0; i < 256; i++) unicode_to_latin1[i] = i; + + /*** ZSCII to character graphics table. ***/ + + build_font3_table(); + + /*** Alphabet table. ***/ + + for(int i = 0; i < 256; i++) atable_pos[i] = -1; + + /* 52 is A2 character 6, which is special and should not + * be matched, so skip over it. + */ + for(int i = 0; i < 52 ; i++) atable_pos[atable[i]] = i; + for(int i = 53; i < 26 * 3; i++) atable_pos[atable[i]] = i; +} + +/* This is adapted from Zip2000 (Copyright 2001 Kevin Bracey). */ +uint16_t unicode_tolower(uint16_t c) +{ + static const unsigned char basic_latin[0x100] = + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xd7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + static const unsigned char latin_extended_a[0x80] = + { + 0x01, 0x01, 0x03, 0x03, 0x05, 0x05, 0x07, 0x07, 0x09, 0x09, 0x0b, 0x0b, 0x0d, 0x0d, 0x0f, 0x0f, + 0x11, 0x11, 0x13, 0x13, 0x15, 0x15, 0x17, 0x17, 0x19, 0x19, 0x1b, 0x1b, 0x1d, 0x1d, 0x1f, 0x1f, + 0x21, 0x21, 0x23, 0x23, 0x25, 0x25, 0x27, 0x27, 0x29, 0x29, 0x2b, 0x2b, 0x2d, 0x2d, 0x2f, 0x2f, + 0x00, 0x31, 0x33, 0x33, 0x35, 0x35, 0x37, 0x37, 0x38, 0x3a, 0x3a, 0x3c, 0x3c, 0x3e, 0x3e, 0x40, + 0x40, 0x42, 0x42, 0x44, 0x44, 0x46, 0x46, 0x48, 0x48, 0x49, 0x4b, 0x4b, 0x4d, 0x4d, 0x4f, 0x4f, + 0x51, 0x51, 0x53, 0x53, 0x55, 0x55, 0x57, 0x57, 0x59, 0x59, 0x5b, 0x5b, 0x5d, 0x5d, 0x5f, 0x5f, + 0x61, 0x61, 0x63, 0x63, 0x65, 0x65, 0x67, 0x67, 0x69, 0x69, 0x6b, 0x6b, 0x6d, 0x6d, 0x6f, 0x6f, + 0x71, 0x71, 0x73, 0x73, 0x75, 0x75, 0x77, 0x77, 0x00, 0x7a, 0x7a, 0x7c, 0x7c, 0x7e, 0x7e, 0x7f + }; + static const unsigned char greek[0x50] = + { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0xac, 0x87, 0xad, 0xae, 0xaf, 0x8b, 0xcc, 0x8d, 0xcd, 0xce, + 0x90, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xa2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf + }; + static const unsigned char cyrillic[0x60] = + { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f + }; + + if (c < 0x0100) c = basic_latin[c]; + else if(c == 0x0130) c = 0x0069; /* capital i with dot -> lower case i */ + else if(c == 0x0178) c = 0x00ff; /* capital y diaeresis -> lower case y diaeresis */ + else if(c < 0x0180) c = latin_extended_a[c - 0x100] + 0x100; + else if(c >= 0x380 && c < 0x3d0) c = greek [c - 0x380] + 0x300; + else if(c >= 0x400 && c < 0x460) c = cyrillic [c - 0x400] + 0x400; + + return c; +} diff --git a/interpreters/bocfel/unicode.h b/interpreters/bocfel/unicode.h new file mode 100644 index 0000000..b421fef --- /dev/null +++ b/interpreters/bocfel/unicode.h @@ -0,0 +1,44 @@ +#ifndef ZTERP_TABLES_H +#define ZTERP_TABLES_H + +#include + +#ifdef ZTERP_GLK +#include +#endif + +#define UNICODE_LINEFEED 10 +#define UNICODE_SPACE 32 +#define UNICODE_QUESTIONMARK 63 + +#define ZSCII_NEWLINE 13 +#define ZSCII_SPACE 32 +#define ZSCII_QUESTIONMARK 63 + +/* This variable controls whether Unicode is used for screen + * output. This affects @check_unicode as well as the ZSCII to + * Unicode table. With Glk it is set based on whether the Glk + * implementation supports Unicode (checked with the Unicode + * gestalt), and determines whether Unicode IO functions should + * be used; otherwise, it is kept in parallel with use_utf8_io. + */ +extern int have_unicode; + +extern uint16_t zscii_to_unicode[]; +extern uint8_t unicode_to_zscii[]; +extern uint8_t unicode_to_zscii_q[]; +extern uint8_t unicode_to_latin1[]; +extern uint16_t zscii_to_font3[]; +extern int atable_pos[]; + +void parse_unicode_table(uint16_t); +void setup_tables(void); + +uint16_t unicode_tolower(uint16_t); + +/* Standard 1.1 notes that Unicode characters 0–31 and 127–159 + * are invalid due to the fact that they’re control codes. + */ +static inline int valid_unicode(uint16_t c) { return (c >= 32 && c <= 126) || c >= 160; } + +#endif diff --git a/interpreters/bocfel/util.c b/interpreters/bocfel/util.c new file mode 100644 index 0000000..6cd3d9a --- /dev/null +++ b/interpreters/bocfel/util.c @@ -0,0 +1,260 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include + +#include "util.h" +#include "screen.h" +#include "unicode.h" +#include "zterp.h" + +#ifdef ZTERP_GLK +#include +#include +#endif + +#ifndef ZTERP_NO_SAFETY_CHECKS +unsigned long zassert_pc; + +void assert_fail(const char *fmt, ...) +{ + va_list ap; + char str[1024]; + + va_start(ap, fmt); + vsnprintf(str, sizeof str, fmt, ap); + va_end(ap); + + snprintf(str + strlen(str), sizeof str - strlen(str), " (pc = 0x%lx)", zassert_pc); + + die("%s", str); +} +#endif + +void warning(const char *fmt, ...) +{ + va_list ap; + char str[1024]; + + va_start(ap, fmt); + vsnprintf(str, sizeof str, fmt, ap); + va_end(ap); + + show_message("WARNING: %s", str); +} + +void die(const char *fmt, ...) +{ + va_list ap; + char str[1024]; + + va_start(ap, fmt); + vsnprintf(str, sizeof str, fmt, ap); + va_end(ap); + + show_message("fatal error: %s", str); + +#ifdef ZTERP_GLK +#ifdef GARGLK + fprintf(stderr, "%s\n", str); +#endif + glk_exit(); +#endif + + exit(EXIT_FAILURE); +} + +/* This is not POSIX compliant, but it gets the job done. + * It should not be called more than once. + */ +static int zoptind = 0; +static const char *zoptarg; +static int zgetopt(int argc, char **argv, const char *optstring) +{ + static const char *p = ""; + const char *optp; + int c; + + if(*p == 0) + { + /* No more arguments. */ + if(++zoptind >= argc) return -1; + + p = argv[zoptind]; + + /* No more options. */ + if(p[0] != '-' || p[1] == 0) return -1; + + /* Handle “--” */ + if(*++p == '-') + { + zoptind++; + return -1; + } + } + + c = *p++; + + optp = strchr(optstring, c); + if(optp == NULL) return '?'; + + if(optp[1] == ':') + { + if(*p != 0) zoptarg = p; + else zoptarg = argv[++zoptind]; + + p = ""; + if(zoptarg == NULL) return '?'; + } + + return c; +} + +char *xstrdup(const char *s) +{ + size_t n; + char *r; + + n = strlen(s) + 1; + + r = malloc(n); + if(r != NULL) memcpy(r, s, n); + + return r; +} + +int process_arguments(int argc, char **argv) +{ + int c; + + while( (c = zgetopt(argc, argv, "a:A:cCdDeE:fFgGiklLmn:N:rR:sS:tT:u:UvxXyz:Z:")) != -1 ) + { + switch(c) + { + case 'a': + options.eval_stack_size = strtol(zoptarg, NULL, 10); + break; + case 'A': + options.call_stack_size = strtol(zoptarg, NULL, 10); + break; + case 'c': + options.disable_color = 1; + break; + case 'C': + options.disable_config = 1; + break; + case 'd': + options.disable_timed = 1; + break; + case 'D': + options.disable_sound = 1; + break; + case 'e': + options.enable_escape = 1; + break; + case 'E': + options.escape_string = xstrdup(zoptarg); + break; + case 'f': + options.disable_fixed = 1; + break; + case 'F': + options.assume_fixed = 1; + break; + case 'g': + options.disable_graphics_font = 1; + break; + case 'G': + options.enable_alt_graphics = 1; + break; + case 'i': + options.show_id = 1; + break; + case 'k': + options.disable_term_keys = 1; + break; + case 'l': + options.disable_utf8 = 1; + break; + case 'L': + options.force_utf8 = 1; + break; + case 'm': + options.disable_meta_commands = 1; + break; + case 'n': + options.int_number = strtol(zoptarg, NULL, 10); + break; + case 'N': + options.int_version = zoptarg[0]; + break; + case 'r': + options.replay_on = 1; + break; + case 'R': + options.replay_name = xstrdup(zoptarg); + break; + case 's': + options.record_on = 1; + break; + case 'S': + options.record_name = xstrdup(zoptarg); + break; + case 't': + options.transcript_on = 1; + break; + case 'T': + options.transcript_name = xstrdup(zoptarg); + break; + case 'u': + options.max_saves = strtol(zoptarg, NULL, 10); + break; + case 'U': + options.disable_undo_compression = 1; + break; + case 'v': + options.show_version = 1; + break; + case 'x': + options.disable_abbreviations = 1; + break; + case 'X': + options.enable_censorship = 1; + break; + case 'y': + options.overwrite_transcript = 1; + break; + case 'z': + options.random_seed = strtol(zoptarg, NULL, 10); + break; + case 'Z': + options.random_device = xstrdup(zoptarg); + break; + default: + return 0; + } + } + + /* Just ignore excess stories for now. */ + if(zoptind < argc) game_file = argv[zoptind]; + + return 1; +} diff --git a/interpreters/bocfel/util.h b/interpreters/bocfel/util.h new file mode 100644 index 0000000..a3047a7 --- /dev/null +++ b/interpreters/bocfel/util.h @@ -0,0 +1,64 @@ +#ifndef ZTERP_UTIL_H +#define ZTERP_UTIL_H + +#ifdef ZTERP_GLK +#include +#endif + +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)) +#define znoreturn __attribute__((__noreturn__)) +#define zprintflike(f, a) __attribute__((__format__(__printf__, f, a))) +#else +#define znoreturn +#define zprintflike(f, a) +#endif + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +#define zexternally_visible __attribute__((__externally_visible__)) +#else +#define zexternally_visible +#endif + +#ifndef ZTERP_NO_SAFETY_CHECKS +extern unsigned long zassert_pc; +#define ZPC(pc) do { zassert_pc = pc; } while(0) + +zprintflike(1, 2) +znoreturn +void assert_fail(const char *, ...); +#define ZASSERT(expr, ...) do { if(!(expr)) assert_fail(__VA_ARGS__); } while(0) +#else +#define ZPC(pc) ((void)0) +#define ZASSERT(expr, ...) ((void)0) +#endif + +zprintflike(1, 2) +void warning(const char *, ...); + +zprintflike(1, 2) +znoreturn +void die(const char *, ...); + +char *xstrdup(const char *); +int process_arguments(int, char **); + +/* Somewhat ugly hack to get around the fact that some Glk functions may + * not exist. These function calls should all be guarded (e.g. + * if(have_unicode), with have_unicode being set iff GLK_MODULE_UNICODE + * is defined) so they will never be called if the Glk implementation + * being used does not support them, but they must at least exist to + * prevent link errors. + */ +#ifdef ZTERP_GLK +#ifndef GLK_MODULE_UNICODE +#define glk_put_char_uni(...) die("bug %s:%d: glk_put_char_uni() called with no unicode", __FILE__, __LINE__) +#define glk_put_string_uni(...) die("bug %s:%d: glk_put_string_uni() called with no unicode", __FILE__, __LINE__) +#define glk_request_char_event_uni(...) die("bug %s:%d: glk_request_char_event_uni() called with no unicode", __FILE__, __LINE__) +#define glk_request_line_event_uni(...) die("bug %s:%d: glk_request_line_event_uni() called with no unicode", __FILE__, __LINE__) +#endif +#ifndef GLK_MODULE_LINE_ECHO +#define glk_set_echo_line_event(...) die("bug: %s %d: glk_set_echo_line_event() called with no line echo", __FILE__, __LINE__) +#endif +#endif + +#endif diff --git a/interpreters/bocfel/zoom.c b/interpreters/bocfel/zoom.c new file mode 100644 index 0000000..e805762 --- /dev/null +++ b/interpreters/bocfel/zoom.c @@ -0,0 +1,48 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include + +#include "zoom.h" +#include "screen.h" +#include "zterp.h" + +static clock_t start_clock, end_clock; + +void zstart_timer(void) +{ + start_clock = clock(); +} + +void zstop_timer(void) +{ + end_clock = clock(); +} + +void zread_timer(void) +{ + store(100 * (end_clock - start_clock) / CLOCKS_PER_SEC); +} + +void zprint_timer(void) +{ + char buf[32]; + snprintf(buf, sizeof buf, "%.2f seconds", (end_clock - start_clock) / (double)CLOCKS_PER_SEC); + for(int i = 0; buf[i] != 0; i++) put_char(buf[i]); +} diff --git a/interpreters/bocfel/zoom.h b/interpreters/bocfel/zoom.h new file mode 100644 index 0000000..9611b83 --- /dev/null +++ b/interpreters/bocfel/zoom.h @@ -0,0 +1,9 @@ +#ifndef ZTERP_ZOOM_H +#define ZTERP_ZOOM_H + +void zstart_timer(void); +void zstop_timer(void); +void zread_timer(void); +void zprint_timer(void); + +#endif diff --git a/interpreters/bocfel/zterp.c b/interpreters/bocfel/zterp.c new file mode 100644 index 0000000..49b84a0 --- /dev/null +++ b/interpreters/bocfel/zterp.c @@ -0,0 +1,1053 @@ +/*- + * Copyright 2009-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel 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 Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "zterp.h" +#include "blorb.h" +#include "branch.h" +#include "io.h" +#include "memory.h" +#include "osdep.h" +#include "process.h" +#include "random.h" +#include "screen.h" +#include "stack.h" +#include "unicode.h" +#include "util.h" + +#ifdef ZTERP_GLK +#include +#include +#ifdef GARGLK +#include +#include + +static schanid_t sound_channel = NULL; +#endif +#endif + +#define MAX_LINE 2048 +#define MAX_PATH 4096 + +#define ZTERP_VERSION "0.6.1" + +const char *game_file; +struct options options = { + .eval_stack_size = DEFAULT_STACK_SIZE, + .call_stack_size = DEFAULT_CALL_DEPTH, + .disable_color = 0, + .disable_config = 0, + .disable_sound = 0, + .disable_timed = 0, + .enable_escape = 0, + .escape_string = NULL, + .disable_fixed = 0, + .assume_fixed = 0, + .disable_graphics_font = 0, + .enable_alt_graphics = 0, + .show_id = 0, + .disable_term_keys = 0, + .disable_utf8 = 0, + .force_utf8 = 0, + .disable_meta_commands = 0, + .int_number = 1, /* DEC */ + .int_version = 'C', + .replay_on = 0, + .replay_name = NULL, + .record_on = 0, + .record_name = NULL, + .transcript_on = 0, + .transcript_name = NULL, + .max_saves = 10, + .disable_undo_compression = 0, + .show_version = 0, + .disable_abbreviations = 0, + .enable_censorship = 0, + .overwrite_transcript = 0, + .random_seed = -1, + .random_device = NULL, +}; + +static char story_id[64]; + +uint32_t pc; + +/* zversion stores the Z-machine version of the story: 1–6. + * + * Z-machine versions 7 and 8 are identical to version 5 but for a + * couple of tiny details. They are thus classified as version 5. + * + * zwhich stores the actual version (1–8) for the few rare times where + * this knowledge is necessary. + */ +int zversion; +static int zwhich; + +struct header header; + +static struct +{ + zterp_io *io; + long offset; +} story; + +/* The null character in the alphabet table does not actually signify a + * null character: character 6 from A2 is special in that it specifies + * that the next two characters form a 10-bit ZSCII character (§3.4). + * The code that uses the alphabet table will step around this character + * when necessary, so it’s safe to use a null character here to mean + * “nothing”. + */ +uint8_t atable[26 * 3] = +{ + /* A0 */ + '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', + + /* A1 */ + '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', + + /* A2 */ + 0x0, 0xd, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', + ',', '!', '?', '_', '#', '\'','"', '/', '\\','-', ':', '(', ')', +}; + +void znop(void) +{ +} + +void zquit(void) +{ + break_from(0); +} + +void zverify(void) +{ + uint16_t checksum = 0; + uint32_t remaining = header.file_length - 0x40; + + if(zterp_io_seek(story.io, story.offset + 0x40, SEEK_SET) == -1) + { + branch_if(0); + return; + } + + while(remaining != 0) + { + uint8_t buf[8192]; + uint32_t to_read = remaining < sizeof buf ? remaining : sizeof buf; + + if(zterp_io_read(story.io, buf, to_read) != to_read) + { + branch_if(0); + return; + } + + for(uint32_t i = 0; i < to_read; i++) checksum += buf[i]; + + remaining -= to_read; + } + + branch_if(checksum == header.checksum); +} + +uint32_t unpack(uint16_t addr, int string) +{ + switch(zwhich) + { + case 1: case 2: case 3: + return addr * 2UL; + case 4: case 5: + return addr * 4UL; + case 6: case 7: + return (addr * 4UL) + (string ? header.S_O : header.R_O); + case 8: + return addr * 8UL; + default: + die("unhandled z-machine version: %d", zwhich); + } +} + +void store(uint16_t v) +{ + store_variable(BYTE(pc++), v); +} + +void zsave5(void) +{ + zterp_io *savefile; + size_t n; + + if(znargs == 0) + { + zsave(); + return; + } + + /* This should be able to suggest a filename, but Glk doesn’t support that. */ + savefile = zterp_io_open(NULL, ZTERP_IO_WRONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + store(0); + return; + } + + ZASSERT(zargs[0] + zargs[1] < memory_size, "attempt to save beyond the end of memory"); + n = zterp_io_write(savefile, &memory[zargs[0]], zargs[1]); + + zterp_io_close(savefile); + + store(n == zargs[1]); +} + +void zrestore5(void) +{ + zterp_io *savefile; + uint8_t *buf; + size_t n; + + if(znargs == 0) + { + zrestore(); + return; + } + + savefile = zterp_io_open(NULL, ZTERP_IO_RDONLY | ZTERP_IO_SAVE); + if(savefile == NULL) + { + store(0); + return; + } + + buf = malloc(zargs[1]); + if(buf == NULL) + { + store(0); + return; + } + + n = zterp_io_read(savefile, buf, zargs[1]); + for(size_t i = 0; i < n; i++) user_store_byte(zargs[0] + i, buf[i]); + + free(buf); + + zterp_io_close(savefile); + + store(n); +} + +void zpiracy(void) +{ + branch_if(1); +} + +void zsound_effect(void) +{ +#ifdef GARGLK + uint8_t repeats, volume; + static uint32_t vols[8] = { + 0x02000, 0x04000, 0x06000, 0x08000, + 0x0a000, 0x0c000, 0x0e000, 0x10000 + }; + + if(sound_channel == NULL || zargs[0] < 3) return; + + repeats = zargs[2] >> 8; + volume = zargs[2] & 0xff; + + if(volume == 0) volume = 1; + if(volume > 8) volume = 8; + + glk_schannel_set_volume(sound_channel, vols[volume - 1]); + + switch(zargs[1]) + { + case 1: /* prepare */ + glk_sound_load_hint(zargs[0], 1); + break; + case 2: /* start */ + glk_schannel_play_ext(sound_channel, zargs[0], repeats == 255 ? -1 : repeats, 0); + break; + case 3: /* stop */ + glk_schannel_stop(sound_channel); + break; + case 4: /* finish with */ + glk_sound_load_hint(zargs[0], 0); + break; + } +#endif +} + +/* Find a story ID roughly in the form of an IFID according to §2.2.2.1 + * of draft 7 of the Treaty of Babel. + * + * This does not add a ZCODE- prefix, and will not search for a manually + * created IFID. + */ +static void find_id(void) +{ + char serial[] = "------"; + + for(int i = 0; i < 6; i++) + { + /* isalnum() cannot be used because it is locale-aware, and this + * must only check for A–Z, a–z, and 0–9. Because ASCII (or a + * compatible charset) is required, testing against 'a', 'z', etc. + * is OK. + */ +#define ALNUM(c) ( ((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= '0' && (c) <= '9') ) + if(ALNUM(header.serial[i])) serial[i] = header.serial[i]; +#undef ALNUM + } + + if(strchr("012345679", serial[0]) != NULL && strcmp(serial, "000000") != 0) + { + snprintf(story_id, sizeof story_id, "%d-%s-%04x", header.release, serial, (unsigned)header.checksum); + } + else + { + snprintf(story_id, sizeof story_id, "%d-%s", header.release, serial); + } +} + +int is_story(const char *id) +{ + return strcmp(story_id, id) == 0; +} + +#ifndef ZTERP_NO_CHEAT +/* The index into these arrays is the address to freeze. + * The first array tracks whether the address is frozen, while the + * second holds the frozen value. + */ +static char freezew_cheat[UINT16_MAX + 1]; +static uint16_t freezew_val [UINT16_MAX + 1]; + +static void cheat(char *how) +{ + char *p; + + p = strtok(how, ":"); + if(p == NULL) return; + + if(strcmp(p, "freezew") == 0) + { + uint16_t addr; + + p = strtok(NULL, ":"); + if(p == NULL) return; + + if(*p == 'G') + { + addr = strtoul(p + 1, NULL, 16); + if(addr > 239) return; + + addr = header.globals + (addr * 2); + } + else + { + addr = strtoul(p, NULL, 16); + } + + p = strtok(NULL, ":"); + if(p == NULL) return; + + freezew_cheat[addr] = 1; + freezew_val [addr] = strtoul(p, NULL, 0); + } +} + +int cheat_find_freezew(uint32_t addr, uint16_t *val) +{ + if(addr > UINT16_MAX || !freezew_cheat[addr]) return 0; + + *val = freezew_val[addr]; + + return 1; +} +#endif + +static void read_config(void) +{ + FILE *fp; + char file[MAX_PATH + 1]; + char line[MAX_LINE]; + char *key, *val, *p; + long n; + int story_matches = 1; + + zterp_os_rcfile(file, sizeof file); + + fp = fopen(file, "r"); + if(fp == NULL) return; + + while(fgets(line, sizeof line, fp) != NULL) + { + line[strcspn(line, "#\n")] = 0; + if(line[0] == 0) continue; + + if(line[0] == '[') + { + p = strrchr(line, ']'); + if(p != NULL && p[1] == 0) + { + *p = 0; + + story_matches = 0; + for(p = strtok(line + 1, " ,"); p != NULL; p = strtok(NULL, " ,")) + { + if(is_story(p)) story_matches = 1; + } + } + + continue; + } + + if(!story_matches) continue; + + key = strtok(line, " \t="); + if(key == NULL) continue; + val = strtok(NULL, "="); + if(val == NULL) continue; + + /* Trim whitespace. */ + while(isspace((unsigned char)*val)) val++; + if(*val == 0) continue; + p = val + strlen(val) - 1; + while(isspace((unsigned char)*p)) *p-- = 0; + + n = strtol(val, NULL, 10); + +#define BOOL(name) else if(strcmp(key, #name) == 0) options.name = (n != 0) +#define NUMBER(name) else if(strcmp(key, #name) == 0) options.name = n +#define STRING(name) else if(strcmp(key, #name) == 0) do { free(options.name); options.name = xstrdup(val); } while(0) +#define CHAR(name) else if(strcmp(key, #name) == 0) options.name = val[0] +#ifdef GARGLK +#define COLOR(name, num)else if(strcmp(key, "color_" #name) == 0) update_color(num, strtol(val, NULL, 16)) +#else +#define COLOR(name, num)else if(0) +#endif + + if(0); + + NUMBER(eval_stack_size); + NUMBER(call_stack_size); + BOOL (disable_color); + BOOL (disable_timed); + BOOL (disable_sound); + BOOL (enable_escape); + STRING(escape_string); + BOOL (disable_fixed); + BOOL (assume_fixed); + BOOL (disable_graphics_font); + BOOL (enable_alt_graphics); + BOOL (disable_term_keys); + BOOL (disable_utf8); + BOOL (force_utf8); + BOOL (disable_meta_commands); + NUMBER(max_saves); + BOOL (disable_undo_compression); + NUMBER(int_number); + CHAR (int_version); + BOOL (replay_on); + STRING(replay_name); + BOOL (record_on); + STRING(record_name); + BOOL (transcript_on); + STRING(transcript_name); + BOOL (disable_abbreviations); + BOOL (enable_censorship); + BOOL (overwrite_transcript); + NUMBER(random_seed); + STRING(random_device); + + COLOR(black, 2); + COLOR(red, 3); + COLOR(green, 4); + COLOR(yellow, 5); + COLOR(blue, 6); + COLOR(magenta, 7); + COLOR(cyan, 8); + COLOR(white, 9); + +#ifndef ZTERP_NO_CHEAT + else if(strcmp(key, "cheat") == 0) cheat(val); +#endif + +#undef BOOL +#undef NUMBER +#undef STRING +#undef CHAR +#undef COLOR + } + + fclose(fp); +} + +static int have_statuswin = 0; +static int have_upperwin = 0; + +/* Various parts of the header (those marked “Rst” in §11) should be + * updated by the interpreter. This function does that. This is also + * used when restoring, because the save file might have come from an + * interpreter with vastly different settings. + */ +void write_header(void) +{ + uint8_t flags1; + + flags1 = BYTE(0x01); + + if(zversion == 3) + { + flags1 |= FLAGS1_NOSTATUS; + flags1 &= ~(FLAGS1_SCREENSPLIT | FLAGS1_VARIABLE); + +#ifdef GARGLK + /* Assume that if Gargoyle is being used, the default font is not fixed. */ + flags1 |= FLAGS1_VARIABLE; +#endif + + if(have_statuswin) flags1 &= ~FLAGS1_NOSTATUS; + if(have_upperwin) flags1 |= FLAGS1_SCREENSPLIT; + if(options.enable_censorship) flags1 |= FLAGS1_CENSOR; + } + else if(zversion >= 4) + { + flags1 |= (FLAGS1_BOLD | FLAGS1_ITALIC | FLAGS1_FIXED); + flags1 &= ~FLAGS1_TIMED; + + if(zversion >= 5) flags1 &= ~FLAGS1_COLORS; + + if(zversion == 6) + { + flags1 &= ~(FLAGS1_PICTURES | FLAGS1_SOUND); +#ifdef GARGLK + if(sound_channel != NULL) flags1 |= FLAGS1_SOUND; +#endif + } + +#ifdef ZTERP_GLK + if(glk_gestalt(gestalt_Timer, 0)) flags1 |= FLAGS1_TIMED; +#ifdef GARGLK + if(zversion >= 5) flags1 |= FLAGS1_COLORS; +#endif +#else + if(!zterp_os_have_style(STYLE_BOLD)) flags1 &= ~FLAGS1_BOLD; + if(!zterp_os_have_style(STYLE_ITALIC)) flags1 &= ~FLAGS1_ITALIC; + if(zversion >= 5 && zterp_os_have_colors()) flags1 |= FLAGS1_COLORS; +#endif + + if(zversion >= 5 && options.disable_color) flags1 &= ~FLAGS1_COLORS; + if(options.disable_timed) flags1 &= ~FLAGS1_TIMED; + if(options.disable_fixed) flags1 &= ~FLAGS1_FIXED; + } + + STORE_BYTE(0x01, flags1); + + if(zversion >= 5) + { + uint16_t flags2 = WORD(0x10); + + flags2 &= ~FLAGS2_MOUSE; +#ifdef GARGLK + if(sound_channel == NULL) flags2 &= ~FLAGS2_SOUND; +#else + flags2 &= ~FLAGS2_SOUND; +#endif + if(zversion >= 6) flags2 &= ~FLAGS2_MENUS; + + if(options.disable_graphics_font) flags2 &= ~FLAGS2_PICTURES; + + if(options.max_saves == 0) flags2 &= ~FLAGS2_UNDO; + + STORE_WORD(0x10, flags2); + } + + if(zversion >= 4) + { + unsigned int width, height; + + /* Interpreter number & version. */ + if(options.int_number < 1 || options.int_number > 11) options.int_number = 1; /* DEC */ + STORE_BYTE(0x1e, options.int_number); + STORE_BYTE(0x1f, options.int_version); + + get_screen_size(&width, &height); + + /* Screen height and width. + * A height of 255 means infinite, so cap at 254. + */ + STORE_BYTE(0x20, height > 254 ? 254 : height); + STORE_BYTE(0x21, width > 255 ? 255 : width); + + if(zversion >= 5) + { + /* Screen width and height in units. */ + STORE_WORD(0x22, width > UINT16_MAX ? UINT16_MAX : width); + STORE_WORD(0x24, height > UINT16_MAX ? UINT16_MAX : height); + + /* Font width and height in units. */ + STORE_BYTE(0x26, 1); + STORE_BYTE(0x27, 1); + + /* Default background and foreground colors. */ + STORE_BYTE(0x2c, 1); + STORE_BYTE(0x2d, 1); + } + } + + /* Standard revision # */ + STORE_BYTE(0x32, 1); + STORE_BYTE(0x33, 1); +} + +void process_story(void) +{ + if(zterp_io_seek(story.io, story.offset, SEEK_SET) == -1) die("unable to rewind story"); + + if(zterp_io_read(story.io, memory, memory_size) != memory_size) die("unable to read from story file"); + + zversion = BYTE(0x00); + if(zversion < 1 || zversion > 8) die("only z-code versions 1-8 are supported"); + + zwhich = zversion; + if(zversion == 7 || zversion == 8) zversion = 5; + + pc = WORD(0x06); + if(pc >= memory_size) die("corrupted story: initial pc out of range"); + + header.release = WORD(0x02); + header.dictionary = WORD(0x08); + header.objects = WORD(0x0a); + header.globals = WORD(0x0c); + header.static_start = WORD(0x0e); + header.abbr = WORD(0x18); + + memcpy(header.serial, &memory[0x12], sizeof header.serial); + + /* There is no explicit “end of static” tag; but it must end by 0xffff + * or the end of the story file, whichever is smaller. + */ + header.static_end = memory_size < 0xffff ? memory_size : 0xffff; + +#define PROPSIZE (zversion <= 3 ? 62L : 126L) + + /* There must be at least enough room in dynamic memory for the header + * (64 bytes), the global variables table (480 bytes), and the + * property defaults table (62 or 126 bytes). + */ + if(header.static_start < 64 + 480 + PROPSIZE) die("corrupted story: dynamic memory too small (%d bytes)", (int)header.static_start); + if(header.static_start >= memory_size) die("corrupted story: static memory out of range"); + + if(header.dictionary != 0 && + header.dictionary < header.static_start) die("corrupted story: dictionary is not in static memory"); + + if(header.objects < 64 || + header.objects + PROPSIZE > header.static_start) + die("corrupted story: object table is not in dynamic memory"); + +#undef PROPSIZE + + if(header.globals < 64 || + header.globals + 480L > header.static_start) die("corrupted story: global variables are not in dynamic memory"); + + if(header.abbr >= memory_size) die("corrupted story: abbreviation table out of range"); + + header.file_length = WORD(0x1a) * (zwhich <= 3 ? 2UL : zwhich <= 5 ? 4UL : 8UL); + if(header.file_length > memory_size) die("story's reported size (%lu) greater than file size (%lu)", (unsigned long)header.file_length, (unsigned long)memory_size); + + header.checksum = WORD(0x1c); + + if(zwhich == 6 || zwhich == 7) + { + header.R_O = WORD(0x28) * 8UL; + header.S_O = WORD(0x2a) * 8UL; + } + + if(dynamic_memory == NULL) + { + dynamic_memory = malloc(header.static_start); + if(dynamic_memory == NULL) die("unable to allocate memory for dynamic memory"); + memcpy(dynamic_memory, memory, header.static_start); + } + +#ifdef GLK_MODULE_LINE_TERMINATORS + if(!options.disable_term_keys) + { + if(zversion >= 5 && WORD(0x2e) != 0) + { + term_keys_reset(); + + for(uint32_t i = WORD(0x2e); i < memory_size && memory[i] != 0; i++) + { + term_keys_add(memory[i]); + } + } + } +#endif + + if(zversion == 1) + { + memcpy(&atable[26 * 2], " 0123456789.,!?_#'\"/\\<-:()", 26); + } + else if(zversion >= 5 && WORD(0x34) != 0) + { + if(WORD(0x34) + 26 * 3 >= memory_size) die("corrupted story: alphabet table out of range"); + + memcpy(atable, &memory[WORD(0x34)], 26 * 3); + + /* Even with a new alphabet table, characters 6 and 7 from A2 must + * remain the same (§3.5.5.1). + */ + atable[52] = 0x00; + atable[53] = 0x0d; + } + + /* Check for a header extension table. */ + if(zversion >= 5) + { + uint16_t etable = WORD(0x36); + + if(etable != 0) + { + uint16_t nentries = user_word(etable); + + if(etable + (2 * nentries) >= memory_size) die("corrupted story: header extension table out of range"); + + /* Unicode table. */ + if(nentries >= 3 && WORD(etable + (2 * 3)) != 0) + { + uint16_t utable = WORD(etable + (2 * 3)); + + parse_unicode_table(utable); + } + + /* Flags3. */ + if(nentries >= 4) STORE_WORD(etable + (2 * 4), 0); + /* True default foreground color. */ + if(nentries >= 5) STORE_WORD(etable + (2 * 5), 0x0000); + /* True default background color. */ + if(nentries >= 6) STORE_WORD(etable + (2 * 6), 0x7fff); + } + } + + /* The configuration file cannot be read until the ID of the current + * story is known, and the ID of the current story is not known until + * the file has been processed; so do both of those here. + */ + find_id(); + if(!options.disable_config) read_config(); + + /* Prevent the configuration file from unexpectedly being reread after + * @restart or @restore. + */ + options.disable_config = 1; + + /* Most options directly set their respective variables, but a few + * require intervention. Delay that intervention until here so that + * the configuration file is taken into account. + */ + if(options.disable_utf8) + { +#ifndef ZTERP_GLK + /* If Glk is not being used, the ZSCII to Unicode table needs to be + * aligned with the IO character set. + */ + have_unicode = 0; +#endif + use_utf8_io = 0; + } + if(options.force_utf8) + { +#ifndef ZTERP_GLK + /* See above. */ + have_unicode = 1; +#endif + use_utf8_io = 1; + } + if(options.escape_string == NULL) options.escape_string = xstrdup("1m"); + +#ifdef GARGLK + if(options.disable_sound && sound_channel != NULL) + { + glk_schannel_destroy(sound_channel); + sound_channel = NULL; + } +#endif + + /* Now that we have a Unicode table and the user’s Unicode + * preferences, build the ZSCII to Unicode and Unicode to ZSCII + * tables. + */ + setup_tables(); + + if(zversion <= 3) have_statuswin = create_statuswin(); + if(zversion >= 3) have_upperwin = create_upperwin(); + + write_header(); + /* Put everything in a clean state. */ + seed_random(0); + init_stack(); + init_screen(); + + /* Unfortunately, Beyond Zork behaves badly when the interpreter + * number is set to DOS: it assumes that it can print out IBM PC + * character codes and get useful results (e.g. it writes out 0x18 + * expecting an up arrow); however, if the pictures bit is set, it + * uses the character graphics font like a good citizen. Thus turn + * that bit on when Beyond Zork is being used and the interpreter is + * set to DOS. It might make sense to do this generally, not just for + * Beyond Zork; but this is such a minor corner of the Z-machine that + * it probably doesn’t matter. For now, peg this to Beyond Zork. + */ + if(options.int_number == 6 && + (is_story("47-870915") || is_story("49-870917") || + is_story("51-870923") || is_story("57-871221"))) + { + STORE_WORD(0x10, WORD(0x10) | FLAGS2_PICTURES); + } + + if(options.transcript_on) + { + STORE_WORD(0x10, WORD(0x10) | FLAGS2_TRANSCRIPT); + options.transcript_on = 0; + } + + if(options.record_on) + { + output_stream(OSTREAM_RECORD, 0); + options.record_on = 0; + } + + if(options.replay_on) + { + input_stream(ISTREAM_FILE); + options.replay_on = 0; + } + + if(zversion == 6) + { + zargs[0] = pc; + call(0); + } +} + +#ifdef ZTERP_GLK +zexternally_visible +void glk_main(void) +#else +int main(int argc, char **argv) +#endif +{ + zterp_blorb *blorb; + +#ifdef ZTERP_GLK + if(!create_mainwin()) return; +#ifdef GLK_MODULE_UNICODE + have_unicode = glk_gestalt(gestalt_Unicode, 0); +#endif +#else + have_unicode = zterp_os_have_unicode(); +#endif + + use_utf8_io = zterp_os_have_unicode(); + +#ifndef ZTERP_GLK + if(!process_arguments(argc, argv)) exit(EXIT_FAILURE); + + zterp_os_init_term(); +#endif + +#ifdef ZTERP_GLK +#define PRINT(s) do { glk_put_string(s); glk_put_char(UNICODE_LINEFEED); } while(0) +#else +#define PRINT(s) puts(s) +#endif + + if(options.show_version) + { + char config[MAX_PATH] = "Configuration file: "; + + PRINT("Bocfel " ZTERP_VERSION); +#ifdef ZTERP_NO_SAFETY_CHECKS + PRINT("Runtime assertions disabled"); +#else + PRINT("Runtime assertions enabled"); +#endif +#ifdef ZTERP_NO_CHEAT + PRINT("Cheat support disabled"); +#else + PRINT("Cheat support enabled"); +#endif +#ifdef ZTERP_TANDY + PRINT("The Tandy bit can be set"); +#else + PRINT("The Tandy bit cannot be set"); +#endif + + zterp_os_rcfile(config + strlen(config), sizeof config - strlen(config)); + PRINT(config); + +#ifdef ZTERP_GLK + glk_exit(); +#else + exit(0); +#endif + } + +#undef PRINT + +#ifdef GARGLK + if(game_file == NULL) + { + frefid_t ref; + + ref = glk_fileref_create_by_prompt(fileusage_Data | fileusage_BinaryMode, filemode_Read, 0); + if(ref != NULL) + { + game_file = xstrdup(garglk_fileref_get_name(ref)); + glk_fileref_destroy(ref); + } + } +#endif + + if(game_file == NULL) die("no story provided"); + + story.io = zterp_io_open(game_file, ZTERP_IO_RDONLY); + if(story.io == NULL) die("cannot open file %s", game_file); + + blorb = zterp_blorb_parse(story.io); + if(blorb != NULL) + { + const zterp_blorb_chunk *chunk; + + chunk = zterp_blorb_find(blorb, BLORB_EXEC, 0); + if(chunk == NULL) die("no EXEC resource found"); + if(strcmp(chunk->name, "ZCOD") != 0) + { + if(strcmp(chunk->name, "GLUL") == 0) die("Glulx stories are not supported (try git or glulxe)"); + + die("unknown story type: %s", chunk->name); + } + + if(chunk->offset > LONG_MAX) die("zcode offset too large"); + + memory_size = chunk->size; + story.offset = chunk->offset; + + zterp_blorb_free(blorb); + } + else + { + long size = zterp_io_filesize(story.io); + + if(size == -1) die("unable to determine file size"); + if(size > UINT32_MAX) die("file too large"); + + memory_size = size; + story.offset = 0; + } + +#ifdef GARGLK + if(glk_gestalt(gestalt_Sound, 0)) + { + /* 5 for the worst case of needing to add .blb to the end plus the + * null character. + */ + char *blorb_file = malloc(strlen(game_file) + 5); + if(blorb_file != NULL) + { + char *p; + strid_t file; + + strcpy(blorb_file, game_file); + p = strrchr(blorb_file, '.'); + if(p != NULL) *p = 0; + strcat(blorb_file, ".blb"); + + file = glkunix_stream_open_pathname(blorb_file, 0, 0); + if(file != NULL) + { + giblorb_set_resource_map(file); + sound_channel = glk_schannel_create(0); + } + + free(blorb_file); + } + } +#endif + + if(memory_size < 64) die("story file too small"); + if(memory_size > SIZE_MAX - 22) die("story file too large"); + + /* It’s possible for a story to be cut short in the middle of an + * instruction. If so, the processing loop will run past the end of + * memory. Either pc needs to be checked each and every time it is + * incremented, or a small guard needs to be placed at the end of + * memory that will trigger an illegal instruction error. The latter + * is done by filling the end of memory with zeroes, which do not + * represent a valid instruction. + * + * There need to be at least 22 bytes for the worst case: 0xec + * (call_vs2) as the last byte in memory. The next two bytes, which + * will be zeroes, indicate that 8 large constants, or 16 bytes, will + * be next. This is a store instruction, so one more byte will be + * read to determine where to store. Another byte is read to + * determine the next opcode; this will be zero, which is nominally a + * 2OP, requiring two more bytes to be read. At this point the opcode + * will be looked up, resulting in an illegal instruction error. + */ + memory = malloc(memory_size + 22); + if(memory == NULL) die("unable to allocate memory for story file"); + memset(memory + memory_size, 0, 22); + + process_story(); + + /* If header transcript/fixed bits have been set, either by the + * story or by the user, this will activate them. + */ + user_store_word(0x10, WORD(0x10)); + + if(options.show_id) + { +#ifdef ZTERP_GLK + glk_put_string(story_id); + glk_exit(); +#else + puts(story_id); + exit(0); +#endif + } + + setup_opcodes(); + + process_instructions(); + +#ifndef ZTERP_GLK + return 0; +#endif +} diff --git a/interpreters/bocfel/zterp.h b/interpreters/bocfel/zterp.h new file mode 100644 index 0000000..2280ef0 --- /dev/null +++ b/interpreters/bocfel/zterp.h @@ -0,0 +1,117 @@ +#ifndef ZTERP_H +#define ZTERP_H + +#include + +struct options +{ + long eval_stack_size; + long call_stack_size; + int disable_color; + int disable_config; + int disable_timed; + int disable_sound; + int enable_escape; + char *escape_string; + int disable_fixed; + int assume_fixed; + int disable_graphics_font; + int enable_alt_graphics; + int show_id; + int disable_term_keys; + int disable_utf8; + int force_utf8; + int disable_meta_commands; + long int_number; + int int_version; + int replay_on; + char *replay_name; + int record_on; + char *record_name; + int transcript_on; + char *transcript_name; + long max_saves; + int disable_undo_compression; + int show_version; + int disable_abbreviations; + int enable_censorship; + int overwrite_transcript; + long random_seed; + char *random_device; +}; + +extern const char *game_file; +extern struct options options; + +/* v3 */ +#define FLAGS1_STATUSTYPE (1U << 1) +#define FLAGS1_STORYSPLIT (1U << 2) +#define FLAGS1_CENSOR (1U << 3) +#define FLAGS1_NOSTATUS (1U << 4) +#define FLAGS1_SCREENSPLIT (1U << 5) +#define FLAGS1_VARIABLE (1U << 6) + +/* v4 */ +#define FLAGS1_COLORS (1U << 0) +#define FLAGS1_PICTURES (1U << 1) +#define FLAGS1_BOLD (1U << 2) +#define FLAGS1_ITALIC (1U << 3) +#define FLAGS1_FIXED (1U << 4) +#define FLAGS1_SOUND (1U << 5) +#define FLAGS1_TIMED (1U << 7) + +#define FLAGS2_TRANSCRIPT (1U << 0) +#define FLAGS2_FIXED (1U << 1) +#define FLAGS2_STATUS (1U << 2) +#define FLAGS2_PICTURES (1U << 3) +#define FLAGS2_UNDO (1U << 4) +#define FLAGS2_MOUSE (1U << 5) +#define FLAGS2_COLORS (1U << 6) +#define FLAGS2_SOUND (1U << 7) +#define FLAGS2_MENUS (1U << 8) + +#define STATUS_IS_TIME() (zversion == 3 && (BYTE(0x01) & FLAGS1_STATUSTYPE)) +#define TIMER_AVAILABLE() (zversion >= 4 && (BYTE(0x01) & FLAGS1_TIMED)) + +struct header +{ + uint16_t release; + uint16_t dictionary; + uint16_t objects; + uint16_t globals; + uint16_t static_start; + uint16_t static_end; + uint16_t abbr; + uint32_t file_length; + uint8_t serial[6]; + uint16_t checksum; + uint32_t R_O; + uint32_t S_O; +}; + +extern uint32_t pc; +extern int zversion; +extern struct header header; +extern uint8_t atable[]; + +int is_story(const char *); + +void write_header(void); + +uint32_t unpack(uint16_t, int); +void store(uint16_t); +void process_story(void); + +#ifndef ZTERP_NO_CHEAT +int cheat_find_freezew(uint32_t, uint16_t *); +#endif + +void znop(void); +void zquit(void); +void zverify(void); +void zpiracy(void); +void zsave5(void); +void zrestore5(void); +void zsound_effect(void); + +#endif diff --git a/libchimara/chimara-if.c b/libchimara/chimara-if.c index 64c32e3..0554d93 100644 --- a/libchimara/chimara-if.c +++ b/libchimara/chimara-if.c @@ -31,13 +31,13 @@ */ static gboolean supported_formats[CHIMARA_IF_NUM_FORMATS][CHIMARA_IF_NUM_INTERPRETERS] = { - /* Frotz Nitfol Glulxe Git */ - { TRUE, TRUE, FALSE, FALSE }, /* Z5 */ - { TRUE, TRUE, FALSE, FALSE }, /* Z6 */ - { TRUE, TRUE, FALSE, FALSE }, /* Z8 */ - { TRUE, TRUE, FALSE, FALSE }, /* Zblorb */ - { FALSE, FALSE, TRUE, TRUE }, /* Glulx */ - { FALSE, FALSE, TRUE, TRUE } /* Gblorb */ + /* Frotz Nitfol Glulxe Git Bocfel */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Z5 */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Z6 */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Z8 */ + { TRUE, TRUE, FALSE, FALSE, TRUE }, /* Zblorb */ + { FALSE, FALSE, TRUE, TRUE, FALSE }, /* Glulx */ + { FALSE, FALSE, TRUE, TRUE, FALSE } /* Gblorb */ }; static gchar *format_names[CHIMARA_IF_NUM_FORMATS] = { N_("Z-code version 5"), @@ -48,10 +48,10 @@ static gchar *format_names[CHIMARA_IF_NUM_FORMATS] = { N_("Blorbed Glulx") }; static gchar *interpreter_names[CHIMARA_IF_NUM_INTERPRETERS] = { - N_("Frotz"), N_("Nitfol"), N_("Glulxe"), N_("Git") + N_("Frotz"), N_("Nitfol"), N_("Glulxe"), N_("Git"), N_("Bocfel") }; static gchar *plugin_names[CHIMARA_IF_NUM_INTERPRETERS] = { - "frotz", "nitfol", "glulxe", "git" + "frotz", "nitfol", "glulxe", "git", "bocfel" }; typedef enum _ChimaraIFFlags { diff --git a/libchimara/chimara-if.h b/libchimara/chimara-if.h index 72d6461..f8ec757 100644 --- a/libchimara/chimara-if.h +++ b/libchimara/chimara-if.h @@ -44,6 +44,7 @@ typedef enum { * @CHIMARA_IF_INTERPRETER_NITFOL: Nitfol * @CHIMARA_IF_INTERPRETER_GLULXE: Glulxe * @CHIMARA_IF_INTERPRETER_GIT: Git + * @CHIMARA_IF_INTERPRETER_BOCFEL: Bocfel * * Constants representing the available interpreter plugins. */ @@ -55,6 +56,7 @@ typedef enum { CHIMARA_IF_INTERPRETER_NITFOL, CHIMARA_IF_INTERPRETER_GLULXE, CHIMARA_IF_INTERPRETER_GIT, + CHIMARA_IF_INTERPRETER_BOCFEL, /*< private >*/ CHIMARA_IF_NUM_INTERPRETERS } ChimaraIFInterpreter; diff --git a/player/preferences.c b/player/preferences.c index 1f39927..a9f5f43 100644 --- a/player/preferences.c +++ b/player/preferences.c @@ -104,11 +104,13 @@ parse_interpreter(const char *interp) return CHIMARA_IF_INTERPRETER_GLULXE; if(strcmp(interp, "git") == 0) return CHIMARA_IF_INTERPRETER_GIT; + if(strcmp(interp, "bocfel") == 0) + return CHIMARA_IF_INTERPRETER_BOCFEL; return CHIMARA_IF_INTERPRETER_NONE; } static const char *interpreter_strings[CHIMARA_IF_NUM_INTERPRETERS] = { - "frotz", "nitfol", "glulxe", "git" + "frotz", "nitfol", "glulxe", "git", "bocfel" }; static const char * @@ -123,7 +125,8 @@ static const char *interpreter_display_strings[CHIMARA_IF_NUM_INTERPRETERS] = { N_("Frotz"), N_("Nitfol"), N_("Glulxe"), - N_("Git") + N_("Git"), + N_("Bocfel") }; static const char *