The Bocfel interpreter is now available for playing Z-machine files.
interpreters/nitfol/Makefile
interpreters/glulxe/Makefile
interpreters/git/Makefile
+interpreters/bocfel/Makefile
tests/Makefile
player/Makefile
player/config.py
-SUBDIRS=nitfol frotz glulxe git
+SUBDIRS=nitfol frotz glulxe git bocfel
-include $(top_srcdir)/git.mk
--- /dev/null
+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.<glk_library_name> 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/<youros>.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.
--- /dev/null
+ 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.
+\f
+ 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.)
+\f
+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.
+\f
+ 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.
+\f
+ 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
+\f
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 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.
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<http://www.gnu.org/licenses/>.
+
+ 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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null
+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
--- /dev/null
+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 <cspiegel@gmail.com>
+http://bocfel.googlecode.com/
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#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;
+}
--- /dev/null
+#ifndef ZTERP_BLORB_H
+#define ZTERP_BLORB_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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]);
+}
--- /dev/null
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#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]);
+}
--- /dev/null
+#ifndef DICTIONARY_H
+#define DICTIONARY_H
+
+#include <stdint.h>
+
+void tokenize(uint16_t, uint16_t, uint16_t, int);
+
+void ztokenise(void);
+void zencode_text(void);
+
+#endif
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <glk.h>
+#include <libchimara/garglk.h>
+
+/* Even on Win32, Gargoyle provides a glkunix startup. */
+#if defined(ZTERP_UNIX) || defined(GARGLK)
+#include <string.h>
+
+#include <glkstart.h>
+
+#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 <windows.h>
+
+#include <WinGlk.h>
+
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#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;
+}
--- /dev/null
+#ifndef ZTERP_IFF_H
+#define ZTERP_IFF_H
+
+#include <stdint.h>
+
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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);
+}
--- /dev/null
+#ifndef ZTERP_IO_H
+#define ZTERP_IO_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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);
+ }
+}
--- /dev/null
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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);
+}
--- /dev/null
+#ifndef ZTERP_MEMORY_H
+#define ZTERP_MEMORY_H
+
+#include <stdint.h>
+
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#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);
+}
--- /dev/null
+#ifndef OBJECTS_H
+#define OBJECTS_H
+
+#include <stdint.h>
+
+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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef ZTERP_UNIX
+#define _XOPEN_SOURCE 600
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+
+#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 <sys/stat.h>
+
+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 <unistd.h>
+#include <sys/ioctl.h>
+#include <curses.h>
+#include <term.h>
+#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
--- /dev/null
+#ifndef ZTERP_OSDEP_H
+#define ZTERP_OSDEP_H
+
+#include <stdio.h>
+#include <stddef.h>
+
+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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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);
+ }
+ }
+}
--- /dev/null
+#ifndef ZTERP_PROCESS_h
+#define ZTERP_PROCESS_H
+
+#include <stdint.h>
+
+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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+#include <time.h>
+
+#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);
+ }
+}
--- /dev/null
+#ifndef ZTERP_MT_H
+#define ZTERP_MT_H
+
+void seed_random(long);
+
+void zrandom(void);
+
+#endif
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#include <libchimara/garglk.h>
+#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);
+}
--- /dev/null
+#ifndef ZTERP_SCREEN_H
+#define ZTERP_SCREEN_H
+
+#include <stdint.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#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);
+}
--- /dev/null
+#ifndef ZTERP_STACK_H
+#define ZTERP_STACK_H
+
+#include <stdint.h>
+
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#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]);
+}
--- /dev/null
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdint.h>
+
+#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;
+}
--- /dev/null
+#ifndef ZTERP_TABLES_H
+#define ZTERP_TABLES_H
+
+#include <stdint.h>
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "util.h"
+#include "screen.h"
+#include "unicode.h"
+#include "zterp.h"
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#include <libchimara/garglk.h>
+#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;
+}
--- /dev/null
+#ifndef ZTERP_UTIL_H
+#define ZTERP_UTIL_H
+
+#ifdef ZTERP_GLK
+#include <glk.h>
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <time.h>
+
+#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]);
+}
--- /dev/null
+#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
--- /dev/null
+/*-
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <signal.h>
+#include <limits.h>
+
+#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 <glk.h>
+#include <libchimara/garglk.h>
+#ifdef GARGLK
+#include <glkstart.h>
+#include <gi_blorb.h>
+
+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
+}
--- /dev/null
+#ifndef ZTERP_H
+#define ZTERP_H
+
+#include <stdint.h>
+
+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
*/
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"),
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 {
* @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.
*/
CHIMARA_IF_INTERPRETER_NITFOL,
CHIMARA_IF_INTERPRETER_GLULXE,
CHIMARA_IF_INTERPRETER_GIT,
+ CHIMARA_IF_INTERPRETER_BOCFEL,
/*< private >*/
CHIMARA_IF_NUM_INTERPRETERS
} ChimaraIFInterpreter;
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 *
N_("Frotz"),
N_("Nitfol"),
N_("Glulxe"),
- N_("Git")
+ N_("Git"),
+ N_("Bocfel")
};
static const char *