//
//  osxtads.c
//  TadsTerp
//
//  Created by Rune Berg on 23/02/14.
//  Copyright (c) 2014 Rune Berg. All rights reserved.
//

#include <stdio.h>
#include "os.h"
#import <Foundation/Foundation.h>
#import "XTAppDelegateProvider.h"
#import "XTAppDelegate.h"
#import "XTGameRunnerProtocol.h"
#import "XTLogger.h"
#import "XTFileUtils.h"
#import "XTConstants.h"


static XTLogger *logger;

#define XTOSIFC_DEF_SELNAME(sn) NSString *selName = sn;
#define XTOSIFC_TRACE_ENTRY(sn) XTOSIFC_DEF_SELNAME(sn); [logger trace:@"%@", XT_SELNAME];
#define XTOSIFC_WARN_ENTRY(sn) XTOSIFC_DEF_SELNAME(sn); [logger warn:@"%@", XT_SELNAME];

#define LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(funcName) \
	{ \
	NSString *msg = [NSString stringWithFormat:@"%@ not implemented!", funcName]; \
	[logger error:msg]; \
	}

void xtads_gen_rand_bytes(unsigned char* buf, size_t len);

static XTAppDelegate *appDelegate;

static NSMutableDictionary *osdirhdlDict; // NSNumber * -> NSDirectoryEnumerator *
static NSInteger nextKeyForOsdirhdlDict;

static const char *tempFilenameTemplate = "/tmp/xtads-temp-XXXXXX";

void resolve_path( char *buf, size_t buflen, const char *path );
unsigned int map_nonlatin1_unicode_char_for_xlat_html4(unsigned int unicode_char);


/****************************************************************************
 *  XTads specific stuff
 */

void osxtads_init()
{
	logger = [XTLogger loggerForName:@"osxtads"];

	osdirhdlDict = [NSMutableDictionary dictionary];
	nextKeyForOsdirhdlDict = 0;
}

id<XTGameRunnerProtocol> getGameRunner()
{
	if (appDelegate == nil) {
		XTAppDelegateProvider *provider = [XTAppDelegateProvider new];
		appDelegate = [provider getAppDelegate];
	}

	id<XTGameRunnerProtocol> gr = [appDelegate getGameRunner];
	return gr;
}

int osxtads_show_terp_copyright_at_game_start()
{
	BOOL res = [getGameRunner() showTerpCopyrightAtGameStart];
	return (int)res;
}


/****************************************************************************
 *  File I/O
 */

/* Open binary file for reading/writing.  If the file already exists,
 * keep the existing contents.  Create a new file if it doesn't already
 * exist.
 */
// amoi calls this
osfildef*
osfoprwb( const char* fname, os_filetype_t notUsed)
{
	XTOSIFC_DEF_SELNAME(@"osfoprwb");
	XT_TRACE_1(@"%s", fname);
	
	osfildef* fp = fopen(fname, "r+b");
	if (fp == 0) fp = fopen(fname, "w+b");
	return fp;
}

/* Open text file for reading and writing, keeping the file's existing
 * contents if the file already exists or creating a new file if no
 * such file exists. 
 */
osfildef*
osfoprwt( const char* fname, os_filetype_t typ)
{
	XTOSIFC_DEF_SELNAME(@"osfoprwt");
	XT_TRACE_2(@"%s %d", fname, typ);
	
	osfildef* fp = fopen(fname, "r+");
	if (fp == 0) {
		fp = fopen(fname, "w+");
	}
	return fp;
}

/*
 *   Create and open a temporary file.  The file must be opened to allow
 *   both reading and writing, and must be in "binary" mode rather than
 *   "text" mode, if the system makes such a distinction.  Returns null on
 *   failure.
 *
 *   If 'fname' is non-null, then this routine should create and open a file
 *   with the given name.  When 'fname' is non-null, this routine does NOT
 *   need to store anything in 'buf'.  Note that the routine shouldn't try
 *   to put the file in a special directory or anything like that; just open
 *   the file with the name exactly as given.
 *
 *   If 'fname' is null, this routine must choose a file name and fill in
 *   'buf' with the chosen name; if possible, the file should be in the
 *   conventional location for temporary files on this system, and should be
 *   unique (i.e., it shouldn't be the same as any existing file).  The
 *   filename stored in 'buf' is opaque to the caller, and cannot be used by
 *   the caller except to pass to osfdel_temp().  On some systems, it may
 *   not be possible to determine the actual filename of a temporary file;
 *   in such cases, the implementation may simply store an empty string in
 *   the buffer.  (The only way the filename would be unavailable is if the
 *   implementation uses a system API that creates a temporary file, and
 *   that API doesn't return the name of the created temporary file.  In
 *   such cases, we don't need the name; the only reason we need the name is
 *   so we can pass it to osfdel_temp() later, but since the system is going
 *   to delete the file automatically, osfdel_temp() doesn't need to do
 *   anything and thus doesn't need the name.)
 *
 *   After the caller is done with the file, it should close the file (using
 *   osfcls() as normal), then the caller MUST call osfdel_temp() to delete
 *   the temporary file.
 *
 *   This interface is intended to take advantage of systems that have
 *   automatic support for temporary files, while allowing implementation on
 *   systems that don't have any special temp file support.  On systems that
 *   do have automatic delete-on-close support, this routine should use that
 *   system-level support, because it helps ensure that temp files will be
 *   deleted even if the caller fails to call osfdel_temp() due to a
 *   programming error or due to a process or system crash.  On systems that
 *   don't have any automatic delete-on-close support, this routine can
 *   simply use the same underlying system API that osfoprwbt() normally
 *   uses (although this routine must also generate a name for the temp file
 *   when the caller doesn't supply one).
 *
 *   This routine can be implemented using ANSI library functions as
 *   follows: if 'fname' is non-null, return fopen(fname,"w+b"); otherwise,
 *   set buf[0] to '\0' and return tmpfile().
 */
osfildef* os_create_tempfile( const char* fname, char* buf )
{
	XTOSIFC_TRACE_ENTRY(@"os_create_tempfile");
	
    if (fname != 0 && fname[0] != '\0') {
        // A filename has been specified; use it.
        return fopen(fname, "w+b");
    }
	
    // No filename needed; create a nameless tempfile.
    buf[0] = '\0';
    return tmpfile();
}

/*
 *   Delete a temporary file - this is used to delete a file created with
 *   os_create_tempfile().  For most platforms, this can simply be defined
 *   the same way as osfdel().  For platforms where the operating system or
 *   file manager will automatically delete a file opened as a temporary
 *   file, this routine should do nothing at all, since the system will take
 *   care of deleting the temp file.
 *
 *   Callers are REQUIRED to call this routine after closing a file opened
 *   with os_create_tempfile().  When os_create_tempfile() is called with a
 *   non-null 'fname' argument, the same value should be passed as 'fname' to
 *   this function.  When os_create_tempfile() is called with a null 'fname'
 *   argument, then the buffer passed in the 'buf' argument to
 *   os_create_tempfile() must be passed as the 'fname' argument here.  In
 *   other words, if the caller explicitly names the temporary file to be
 *   opened in os_create_tempfile(), then that same filename must be passed
 *   here to delete the named file; if the caller lets os_create_tempfile()
 *   generate a filename, then the generated filename must be passed to this
 *   routine.
 *
 *   If os_create_tempfile() is implemented using ANSI library functions as
 *   described above, then this routine can also be implemented with ANSI
 *   library calls as follows: if 'fname' is non-null and fname[0] != '\0',
 *   then call remove(fname); otherwise do nothing.
 */
int osfdel_temp( const char* fname )
{
	XTOSIFC_TRACE_ENTRY(@"osfdel_temp");
	
	int res = 0; //success
	
	if (fname != 0 && fname[0] != '\0') {
		res = remove(fname);
	}
	
	return res;
}

/*
 *   Generate a name for a temporary file.  This constructs a random file
 *   path in the system temp directory that isn't already used by an existing
 *   file.
 *
 *   On systems with long filenames, this can be implemented by selecting a
 *   GUID-strength random name (such as 32 random hex digits) with a decent
 *   random number generator.  That's long enough that the odds of a
 *   collision are essentially zero.  On systems that only support short
 *   filenames, the odds of a collision are non-zero, so the routine should
 *   actually check that the chosen filename doesn't exist.
 *
 *   Optionally, before returning, this routine *may* create (and close) an
 *   empty placeholder file to "reserve" the chosen filename.  This isn't
 *   required, and on systems with long filenames it's usually not necessary
 *   because of the negligible chance of a collision.  On systems with short
 *   filenames, a placeholder can be useful to prevent a subsequent call to
 *   this routine, or a separate process, from using the same filename before
 *   the caller has had a chance to use the returned name to create the
 *   actual temp file.
 *
 *   Returns true on success, false on failure.  This can fail if there's no
 *   system temporary directory defined, or the temp directory is so full of
 *   other files that we can't find an unused filename.
 */
int
os_gen_temp_filename( char* buf, size_t buflen )
{
	XTOSIFC_DEF_SELNAME(@"os_gen_temp_filename");
	
	int res = 0;
	
	char *tempFilenameBuffer = malloc(strlen(tempFilenameTemplate) * 2);
	if (tempFilenameBuffer != NULL) {
		strcpy(tempFilenameBuffer, tempFilenameTemplate);
		char *tempFilename = mktemp(tempFilenameBuffer);
		if (tempFilename != NULL) {
			XT_TRACE_1(@"tempFilename=\"%s\"", tempFilename);
			if (strlen(tempFilename) < buflen) {
				strcpy(buf, tempFilename);
				res = 1;
			}
		}
		free(tempFilenameBuffer);
	}
	
	XT_TRACE_1(@"--> %d", res);
	return res;
}

/* Get full stat() information on a file.
 *
 * (Basically copied from osportable.cc, then de-C++'d a bit, tweaked for OS X, and given logging.)
 */
int
os_file_stat( const char *fname, int follow_links, os_file_stat_t *s )
{
	XTOSIFC_DEF_SELNAME(@"os_file_stat");
	XT_TRACE_2(@"fname=%s follow_links=%d", fname, follow_links);

	struct stat buf;
	int statRes;
	if (follow_links) {
		statRes = stat(fname, &buf);
	} else {
		statRes = lstat(fname, &buf);
	}
	if (statRes != 0) {
		XT_TRACE_0(@"-> 0 (stat/lstat returned 0)");
		return 0;
	}
	
    s->sizelo = (uint32_t)(buf.st_size & 0xFFFFFFFF);
    s->sizehi = sizeof(buf.st_size) > 4
				? (uint32_t)((buf.st_size >> 32) & 0xFFFFFFFF)
				: 0;
    s->cre_time = buf.st_ctime;
    s->mod_time = buf.st_mtime;
    s->acc_time = buf.st_atime;
	
	[XTFileUtils stat:&buf toMode:&(s->mode) forFileName:fname];

	char *filenNameWoPath = os_get_root_name(fname);
	int res = [XTFileUtils stat:&buf toAttrs:&(s->attrs) forFileName:filenNameWoPath];
	
	XT_TRACE_1(@"-> %d", res);
	return res;
}

/*
 *   Get a file's mode and attribute flags.  This retrieves information on
 *   the given file equivalent to the st_mode member of the 'struct stat'
 *   data returned by the Unix stat() family of functions, as well as some
 *   extra system-specific attributes.  On success, fills in *mode (if mode
 *   is non-null) with the mode information as a bitwise combination of
 *   OSFMODE_xxx values, fills in *attr (if attr is non-null) with a
 *   combination of OSFATTR_xxx attribute flags, and returns true; on
 *   failure, simply returns false.  Failure can occur if the file doesn't
 *   exist, can't be accessed due to permissions, etc.
 *
 *   Note that 'mode' and/or 'attr' can be null if the caller doesn't need
 *   that information.  Implementations must check these parameters for null
 *   pointers and skip returning the corresponding information if null.
 *
 *   If the file in 'fname' is a symbolic link, the behavior depends upon
 *   'follow_links'.  If 'follow_links' is true, the function should resolve
 *   the link reference (and if that points to another link, the function
 *   resolves that link as well, and so on) and return information on the
 *   object the link points to.  Otherwise, the function returns information
 *   on the link itself.  This only applies for symbolic links (not for hard
 *   links), and only if the underlying OS and file system support this
 *   distinction; if the OS transparently resolves links and doesn't allow
 *   retrieving information about the link itself, 'follow_links' can be
 *   ignored.  Likewise, hard links (on systems that support them) are
 *   generally indistinguishable from regular files, so this function isn't
 *   expected to do anything special with them.
 *
 *   The '*mode' value returned is a bitwise combination of OSFMODE_xxx flag.
 *   Many of the flags are mutually exclusive; for example, "file" and
 *   "directory" should never be combined.  It's also possible for '*mode' to
 *   be zero for a valid file; this means that the file is of some special
 *   type on the local system that doesn't fit any of the OSFMODE_xxx types.
 *   (If any ports do encounter such cases, we can add OSFMODE_xxx types to
 *   accommodate new types.  The list below isn't meant to be final; it's
 *   just what we've encountered so far on the platforms where TADS has
 *   already been ported.)
 *
 *   The OSFMODE_xxx values are left for the OS to define so that they can be
 *   mapped directly to the OS API's equivalent constants, if desired.  This
 *   makes the routine easy to write, since you can simply set *mode directly
 *   to the mode information the OS returns from its stat() or equivalent.
 *   However, note that these MUST be defined as bit flags - that is, each
 *   value must be exactly a power of 2.  Windows and Unix-like systems
 *   follow this practice, as do most "stat()" functions in C run-time
 *   libraries, so this usually works automatically if you map these
 *   constants to OS or C library values.  However, if a port defines its own
 *   values for these, take care that they're all powers of 2.
 *
 *   Obviously, a given OS might not have all of the file types listed here.
 *   If any OSFMODE_xxx values aren't applicable on the local OS, you can
 *   simply define them as zero since they'll never be returned.
 *
 *   Notes on attribute flags:
 *
 *   OSFATTR_HIDDEN means that the file is conventionally hidden by default
 *   in user interface views or listings, but is still fully accessible to
 *   the user.  Hidden files are also usually excluded by default from
 *   wildcard patterns in commands ("rm *.*").  On Unix, a hidden file is one
 *   whose name starts with "."; on Windows, it's a file with the HIDDEN bit
 *   in its file attributes.  On systems where this concept exists, the user
 *   can still manipulate these files as normal by naming them explicitly,
 *   and can typically make them appear in UI views or directory listings via
 *   a preference setting or command flag (e.g., "ls -a" on Unix).  The
 *   "hidden" flag is explicitly NOT a security or permissions mechanism, and
 *   it doesn't protect the file against intentional access by a user; it's
 *   merely a convenience designed to reduce clutter by excluding files
 *   maintained by the OS or by an application (such as preference files,
 *   indices, caches, etc) from casual folder browsing, where a user is
 *   typically only concerned with her own document files.  On systems where
 *   there's no such naming convention or attribute metadata, this flag will
 *   never appear.
 *
 *   OSFATTR_SYSTEM is similar to 'hidden', but means that the file is
 *   specially marked as an operating system file.  This is mostly a
 *   DOS/Windows concept, where it corresponds to the SYSTEM bit in the file
 *   attributes; this flag will probably never appear on other systems.  The
 *   distinction between 'system' and 'hidden' is somewhat murky even on
 *   Windows; most 'system' file are also marked as 'hidden', and in
 *   practical terms in the user interface, 'system' files are treated the
 *   same as 'hidden'.
 *
 *   OSFATTR_READ means that the file is readable by this process.
 *
 *   OSFATTR_WRITE means that the file is writable by this process.
 */
int
osfmode( const char* fname, int follow_links, unsigned long* mode, unsigned long* attr )
{
	XTOSIFC_DEF_SELNAME(@"osfmode");
	XT_TRACE_2(@"fname=%s follow_links=%d", fname, follow_links);
	
	// Find the stat to report on
	//---------------------------
	
	struct stat stat;
	
	int statRes = lstat(fname, &stat); // if fname is a symlink, stat contains info about the *symlink*
	if (statRes != 0) {
		XT_TRACE_1(@"-> 0 (lstat failed for %s)", fname);
		return 0;
	}
	
	const char* fileNameReportedOn = fname;
	char resolvedFilename[1024];

	if (follow_links) {
		if (S_ISLNK(stat.st_mode)) {
			if ([XTFileUtils resolveLinkFully:fname toFilename:resolvedFilename ofMaxLength:sizeof(resolvedFilename)]) {
				statRes = lstat(resolvedFilename, &stat);
				if (statRes != 0) {
					XT_TRACE_1(@"-> 0 (lstat failed for %s)", resolvedFilename);
					return 0;
				}
				XT_TRACE_2(@"\"%s\" resolved to \"%s\"", fname, resolvedFilename);
				fileNameReportedOn = resolvedFilename;
			} else {
				XT_TRACE_1(@"-> 0 (resolveLinkFully failed for \"%s\")", fname);
				return 0;
			}
		}
	} else {
		int brkpt = 1;
	}

	// Report on the stat found
	//-------------------------

	if (mode != NULL) {
		[XTFileUtils stat:&stat toMode:mode forFileName:fileNameReportedOn];
	}
	if (attr != NULL) {
		if (! [XTFileUtils stat:&stat toAttrs:attr forFileName:fileNameReportedOn]) {
			return 0;
		}
		XT_TRACE_1(@"*attr=%lu", *attr);
	}

	XT_TRACE_0(@"-> 1");

	return 1;
}

/*
 *   Manually resolve a symbolic link.  If the local OS and file system
 *   support symbolic links, and the given filename is a symbolic link (in
 *   which case osfmode(fname, FALSE, &m, &a) will set OSFMODE_LINK in the
 *   mode bits), this fills in 'target' with the name of the link target
 *   (i.e., the object that the link in 'fname' points to).  This should
 *   return a fully qualified file system path.  Returns true on success,
 *   false on failure.
 *
 *   This should only resolve a single level of indirection.  If the link
 *   target of 'fname' is itself a link to a second target, this should only
 *   resolve the single reference from 'fname' to its direct direct.  Callers
 *   that wish to resolve the final target of a chain of link references must
 *   iterate until the returned path doesn't refer to a link.
 */
int
os_resolve_symlink( const char* fname, char* target, size_t target_size )
{
	XTOSIFC_DEF_SELNAME(@"os_resolve_symlink");
	XT_TRACE_1(@"\"%s\"", fname);
	
	//TODO ideally, only resolve *one* link level like spec. says
	
	int res = 0;
	
	if ([XTFileUtils resolveLinkFully:fname toFilename:target ofMaxLength:target_size]) {
		res = 1;
		XT_TRACE_2(@"\"%s\" resolved to \"%s\"", fname, target);
	}
	
	XT_TRACE_1(@"-> %d", res);
	return res;
}

/*
 *   Get the full filename (including directory path) to the executable
 *   file, given the argv[0] parameter passed into the main program.  This
 *   fills in the buffer with a null-terminated string that can be used in
 *   osfoprb(), for example, to open the executable file.
 *
 *   Returns non-zero on success.  If it's not possible to determine the
 *   name of the executable file, returns zero.
 *
 *   Some operating systems might not provide access to the executable file
 *   information, so non-trivial implementation of this routine is optional;
 *   if the necessary information is not available, simply implement this to
 *   return zero.  If the information is not available, callers should offer
 *   gracefully degraded functionality if possible.
 */
int os_get_exe_filename( char* buf, size_t buflen, const char* argv0)
{
	// This terp is an OS X app bundle, not a regular file, so:
	return 0;
}

/*
 *   Duplicate a file handle.  Returns a new osfildef* handle that accesses
 *   the same open file as an existing osfildef* handle.  The new handle is
 *   independent of the original handle, with its own seek position,
 *   buffering, etc.  The new handle and the original handle must each be
 *   closed separately when the caller is done with them (closing one doesn't
 *   close the other).  The effect should be roughly the same as the Unix
 *   dup() function.
 *
 *   On success, returns a new, non-null osfildef* handle duplicating the
 *   original handle.  Returns null on failure.
 *
 *   'mode' is a simplified stdio fopen() mode string.  The first
 *   character(s) indicate the access type: "r" for read access, "w" for
 *   write access, or "r+" for read/write access.  Note that "w+" mode is
 *   specifically not defined, since the fopen() handling of "w+" is to
 *   truncate any existing file contents, which is not desirable when
 *   duplicating a handle.  The access type can optionally be followed by "t"
 *   for text mode, "s" for source file mode, or "b" for binary mode, with
 *   the same meanings as for the various osfop*() functions.  The default is
 *   't' for text mode if none of these are specified.
 *
 *   If the osfop*() functions are implemented in terms of stdio FILE*
 *   objects, this can be implemented as fdopen(dup(fileno(orig)), mode), or
 *   using equivalents if the local stdio library uses different names for
 *   these functions.  Note that "s" (source file format) isn't a stdio mode,
 *   so implementations must translate it to the appropriate "t" or "b" mode.
 *   (For that matter, "t" and "b" modes aren't universally supported either,
 *   so some implementations may have to translate these, or more likely
 *   simply remove them, as most platforms don't distinguish text and binary
 *   modes anyway.)
 */
// This impl. is a copy of that in frobtads' osportable.cc (by Nikos Chantziaras).
osfildef*
osfdup( osfildef* orig, const char* mode )
{
	char realmode[5];
	char *p = realmode;
	const char *m;
	
	/* verify that there aren't any unrecognized mode flags */
	for (m = mode ; *m != '\0' ; ++m)
	{
		if (strchr("rw+bst", *m) == 0)
			return 0;
	}
	
	/* figure the read/write mode - translate r+ and w+ to r+ */
	if ((mode[0] == 'r' || mode[0] == 'w') && mode[1] == '+')
		*p++ = 'r', *p++ = '+';
	else if (mode[0] == 'r')
		*p++ = 'r';
	else if (mode[0] == 'w')
		*p++ = 'w';
	else
		return 0;
	
	/* end the mode string */
	*p = '\0';
	
	/* duplicate the handle in the given mode */
	return fdopen(dup(fileno(orig)), mode);
}

/*
 *   Get the absolute, fully qualified filename for a file.  This fills in
 *   'result_buf' with the absolute path to the given file, taking into
 *   account the current working directory and any other implied environment
 *   information that affects the way the file system would resolve the given
 *   file name to a specific file on disk if we opened the file now using
 *   this name.
 *
 *   The returned path should be in absolute path form, meaning that it's
 *   independent of the current working directory or any other environment
 *   settings.  That is, this path should still refer to the same file even
 *   if the working directory changes.
 *
 *   Note that it's valid to get the absolute path for a file that doesn't
 *   exist, or for a path with directory components that don't exist.  For
 *   example, a caller might generate the absolute path for a file that it's
 *   about to create, or a hypothetical filename for path comparison
 *   purposes.  The function should succeed even if the file or any path
 *   components don't exist.  If the file is in relative format, and any path
 *   elements don't exist but are syntactically well-formed, the result
 *   should be the path obtained from syntactically combining the working
 *   directory with the relative path.
 *
 *   On many systems, a given file might be reachable through more than one
 *   absolute path.  For example, on Unix it might be possible to reach a
 *   file through symbolic links to the file itself or to parent directories,
 *   or hard links to the file.  It's up to the implementation to determine
 *   which path to use in such cases.
 *
 *   On success, returns true.  If it's not possible to resolve the file name
 *   to an absolute path, the routine copies the original filename to the
 *   result buffer exactly as given, and returns false.
 */
int
os_get_abs_filename( char* result_buf, size_t result_buf_size, const char* filename )
{
	// Impl. copied from frobtads osportable.cc:

	XTOSIFC_DEF_SELNAME(@"os_get_abs_filename");
	
	//NSString *ocFilename = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(filename);
	XT_TRACE_1(@"filename=\"%s\"", filename);
	
	// If the filename is already absolute, copy it; otherwise combine
	// it with the working directory.
	if (os_is_file_absolute(filename))
	{
		// absolute - copy it as-is
		safe_strcpy(result_buf, result_buf_size, filename);
	}
	else
	{
		// combine it with the working directory to get the path
		char pwd[OSFNMAX];
		if (getcwd(pwd, sizeof(pwd)) != 0)
			os_build_full_path(result_buf, result_buf_size, pwd, filename);
		else
			safe_strcpy(result_buf, result_buf_size, filename);
	}
	
	// canonicalize the result
	canonicalize_path(result_buf);
	
	// Try getting the canonical path from the OS (allocating the
	// result buffer).
	char* newpath = realpath(filename, 0);
	if (newpath != 0) {
		// Copy the output (truncating if it's too long).
		safe_strcpy(result_buf, result_buf_size, newpath);
		free(newpath);
		return 1;
	}
	
	// realpath() failed, but that's okay - realpath() only works if the
	// path refers to an existing file, but it's valid for callers to
	// pass non-existent filenames, such as names of files they're about
	// to create, or hypothetical paths being used for comparison
	// purposes or for future use.  Simply return the canonical path
	// name we generated above.
	return 1;
}

/*
 *   Determine if the given filename refers to a special file.  Returns the
 *   appropriate enum value if so, or OS_SPECFILE_NONE if not.  The given
 *   filename must be a root name - it must not contain a path prefix.  The
 *   purpose here is to classify the results from os_find_first_file() and
 *   os_find_next_file() to identify the special relative links, so callers
 *   can avoid infinite recursion when traversing a directory tree.
 */
enum os_specfile_t
os_is_special_file( const char* fname )
{
	// Impl. basically copied from frobtads osportable.cc:
	
	XTOSIFC_DEF_SELNAME(@"os_is_special_file");
	XT_TRACE_1(@"%s", fname);
	
	const char selfWithSep[] = {'.', OSPATHCHAR, 0};
	const char parentWithSep[] = {'.', '.', OSPATHCHAR, 0};
	
	enum os_specfile_t res = OS_SPECFILE_NONE;
	
	if ((strcmp(fname, ".") == 0) || (strcmp(fname, selfWithSep) == 0)) {
		res = OS_SPECFILE_SELF;
		
	} else if ((strcmp(fname, "..") == 0) || (strcmp(fname, parentWithSep) == 0)) {
		res = OS_SPECFILE_PARENT;
	}
	
	XT_TRACE_1(@"-> %d", res);
	return res;
}

/* Seek to the resource file embedded in the current executable file.
 *
 * We don't support this (and probably never will).
 */
osfildef*
os_exeseek( const char* p1, const char* p2)
{
	LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_exeseek");
	
	return 0;
}


/****************************************************************************
 *  Directory I/O
 */

/* Create a directory.
 */
int
os_mkdir( const char* dir, int create_parents )
{
	NSString *dirString = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(dir);
	XTOSIFC_DEF_SELNAME(@"os_mkdir");
	XT_TRACE_2(@"%@ %d", dirString, create_parents);
	
	NSURL *url = [XTFileUtils urlForDirectory:dirString];
	BOOL res = [XTFileUtils createDirectoryAtUrl:url withIntermediateDirectories:create_parents];
	
	return res;
}

/*
 *   Remove a directory.  Returns true on success, false on failure.
 *
 *   If the directory isn't already empty, this routine fails.  That is, the
 *   routine does NOT recursively delete the contents of a non-empty
 *   directory.  It's up to the caller to delete any contents before removing
 *   the directory, if that's the caller's intention.  (Note to implementors:
 *   most native OS APIs to remove directories fail by default if the
 *   directory isn't empty, so it's usually safe to implement this simply by
 *   calling the native API.  However, if your system's version of this API
 *   can remove a non-empty directory, you MUST add an extra test before
 *   removing the directory to ensure it's empty, and return failure if it's
 *   not.  For the purposes of this test, "empty" should of course ignore any
 *   special objects that are automatically or implicitly present in all
 *   directories, such as the Unix "." and ".." relative links.)
 */
int
os_rmdir( const char* dir )
{
	NSString *dirString = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(dir);
	XTOSIFC_DEF_SELNAME(@"os_rmdir");
	XT_TRACE_1(@"%@", dirString);
	
	NSURL *url = [XTFileUtils urlForDirectory:dirString];
	BOOL res = [XTFileUtils removeDirectoryAtUrl:url];
	
	XT_TRACE_1(@"-> %d", res);
	
	return res;
}

/*
 *   Open a directory.  This begins an enumeration of a directory's contents.
 *   'dirname' is a relative or absolute path to a directory.  On success,
 *   returns true, and 'handle' is set to a port-defined handle value that's
 *   used in subsequent calls to os_read_dir() and os_close_dir().  Returns
 *   false on failure.
 *
 *   If the routine succeeds, the caller must eventually call os_close_dir()
 *   to release the resources associated with the handle.
 */
int
os_open_dir( const char* dirname, osdirhdl_t* handle )
{
	XTOSIFC_DEF_SELNAME(@"os_open_dir");
	XT_TRACE_1(@"%s", dirname);
	
	// NSFileManager's enumeratorAtURL doesn't resolve symlinks, so do it ourselves
	char dirnameResolved[1024];
	BOOL resolvedOk = [XTFileUtils resolveLinkFully:dirname toFilename:dirnameResolved ofMaxLength:sizeof(dirnameResolved)];
	if (! resolvedOk) {
		XT_ERROR_0(@"failed to resolve");
		return 0;
	}
	
	NSString *dirnameResolvedString = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(dirnameResolved);
	NSURL *dirUrl = [XTFileUtils urlForDirectory:dirnameResolvedString];
	
	NSFileManager *fileMgr = [NSFileManager defaultManager];
	
	NSUInteger options = NSDirectoryEnumerationSkipsSubdirectoryDescendants |
	//NSDirectoryEnumerationSkipsHiddenFiles |
	NSDirectoryEnumerationSkipsPackageDescendants;
	
	NSDirectoryEnumerator *dirEnumerator = [fileMgr enumeratorAtURL:dirUrl
										 includingPropertiesForKeys:nil
															options:options
													   errorHandler:^(NSURL *url, NSError *error) {
														   //TODO? XT_ERROR_2(@"%@ (%@)", [error localizedDescription], url);
														   return YES; // continue after the error
													   }];
	
	// Passing a NSDirectoryEnumerator* out of (and back into) ARC scope is not trivial,
	// so instead we put it in an in-ARC map and pass out an int key,
	// so that we can look up the NSDirectoryEnumerator* in os_read_dir() and os_close_dir(),
	// using said int key.
	//
	NSNumber *keyForOsdirhdlDict = [NSNumber numberWithInteger:nextKeyForOsdirhdlDict];
	osdirhdlDict[keyForOsdirhdlDict] = dirEnumerator;
	*handle = nextKeyForOsdirhdlDict;
	
	XT_TRACE_1(@"put NSDirectoryEnumerator with key=%d", nextKeyForOsdirhdlDict);
	
	nextKeyForOsdirhdlDict += 1;
	
	XT_TRACE_0(@"-> 1");
	return 1;
}

/*
 *   Read the next file in a directory.  'handle' is a handle value obtained
 *   from a call to os_open_dir().  On success, returns true and fills in
 *   'fname' with the next filename; the handle is also internally updated so
 *   that the next call to this function will retrieve the next file, and so
 *   on until all files have been retrieved.  If an error occurs, or there
 *   are no more files in the directory, returns false.
 *
 *   The filename returned is the root filename only, without the path.  The
 *   caller can build the full path by calling os_build_full_path() or
 *   os_combine_paths() with the original directory name and the returned
 *   filename as parameters.
 *
 *   This routine lists all objects in the directory that are visible to the
 *   corresponding native API, and is non-recursive.  The listing should thus
 *   include subdirectory objects, but not the contents of subdirectories.
 *   Implementations are encouraged to simply return all objects returned
 *   from the corresponding native directory scan API; there's no need to do
 *   any filtering, except perhaps in cases where it's difficult or
 *   impossible to represent an object in terms of the osifc APIs (e.g., it
 *   might be reasonable to exclude files without names).  System relative
 *   links, such as the Unix/DOS "." and "..", specifically should be
 *   included in the listing.  For unusual objects that don't fit into the
 *   os_file_stat() taxonomy or that otherwise might create confusion for a
 *   caller, err on the side of full disclosure (i.e., just return everything
 *   unfiltered); if necessary, we can extend the os_file_stat() taxonomy or
 *   add new osifc APIs to create a portable abstraction to handle whatever
 *   is unusual or potentially confusing about the native object.  For
 *   example, Unix implementations should feel free to return symbolic link
 *   objects, including dangling links, since we have the portable
 *   os_resolve_symlink() that lets the caller examine the meaning of the
 *   link object.
 */
int
os_read_dir( osdirhdl_t handle, char* fname, size_t fname_size )
{
	XTOSIFC_TRACE_ENTRY(@"os_read_dir");
	
	NSInteger keyAsint = handle;
	NSNumber *keyForOsdirhdlDict = [NSNumber numberWithInteger:keyAsint];
	NSDirectoryEnumerator *dirEnumerator = osdirhdlDict[keyForOsdirhdlDict];
	
	XT_TRACE_1(@"retrieved NSDirectoryEnumerator with key=%d", keyAsint);
	if (dirEnumerator == nil) {
		XT_ERROR_0(@"dirEnumerator == nil, -> 0");
		return 0;
	}
	
	NSURL *nextEntry = [dirEnumerator nextObject];
	
	int res = 0;
	
	if (nextEntry != nil) {
		NSString *urlNSString = [nextEntry lastPathComponent];
		if (urlNSString != nil) {
			const char* urlCString = XTADS_NSSTRING_TO_FILESYSTEM_C_STRING(urlNSString);
			if (strlen(urlCString) < fname_size) {
				strcpy(fname, urlCString);
				res = 1;
				XT_TRACE_2(@"fname=\"%s\" %d", urlCString, res);
			} else {
				XT_ERROR_2(@"strlen(\"%s\") < %u", urlCString, fname_size);
			}
		}
	} else {
		XT_TRACE_0(@"nextEntry == nil");
	}
	
	XT_TRACE_1(@"-> %d", res);
	
	return res;
}

/*
 *   Close a directory handle.  This releases the resources associated with a
 *   directory search started with os_open_dir().  Every successful call to
 *   os_open_dir() must have a matching call to os_close_dir().  As usual for
 *   open/close protocols, the handle is invalid after calling this function,
 *   so no more calls to os_read_dir() may be made with the handle.
 */
void
os_close_dir( osdirhdl_t handle )
{
	XTOSIFC_DEF_SELNAME(@"os_close_dir");
	
	NSInteger keyAsint = handle;
	NSNumber *keyForOsdirhdlDict = [NSNumber numberWithInteger:keyAsint];
	XT_TRACE_1(@"retrieved NSDirectoryEnumerator with key=%d", keyAsint);
	
	if (osdirhdlDict[keyForOsdirhdlDict] == nil) {
		XT_ERROR_1(@"nil value for key %@", keyForOsdirhdlDict);
	} else {
		[osdirhdlDict removeObjectForKey:keyForOsdirhdlDict];
	}
}

/*
 *   Determine if the given file is in the given directory.  Returns true if
 *   so, false if not.  'filename' is a relative or absolute file name;
 *   'path' is a relative or absolute directory path, such as one returned
 *   from os_get_path_name().
 *
 *   If 'include_subdirs' is true, the function returns true if the file is
 *   either directly in the directory 'path', OR it's in any subdirectory of
 *   'path'.  If 'include_subdirs' is false, the function returns true only
 *   if the file is directly in the given directory.
 *
 *   If 'match_self' is true, the function returns true if 'filename' and
 *   'path' are the same directory; otherwise it returns false in this case.
 *
 *   This routine is allowed to return "false negatives" - that is, it can
 *   claim that the file isn't in the given directory even when it actually
 *   is.  The reason is that it's not always possible to determine for sure
 *   that there's not some way for a given file path to end up in the given
 *   directory.  In contrast, a positive return must be reliable.
 *
 *   If possible, this routine should fully resolve the names through the
 *   file system to determine the path relationship, rather than merely
 *   analyzing the text superficially.  This can be important because many
 *   systems have multiple ways to reach a given file, such as via symbolic
 *   links on Unix; analyzing the syntax alone wouldn't reveal these multiple
 *   pathways.
 *
 *   SECURITY NOTE: If possible, implementations should fully resolve all
 *   symbolic links, relative paths (e.g., Unix ".."), etc, before rendering
 *   judgment.  One important application for this routine is to determine if
 *   a file is in a sandbox directory, to enforce security restrictions that
 *   prevent a program from accessing files outside of a designated folder.
 *   If the implementation fails to resolve symbolic links or relative paths,
 *   a malicious program or user could bypass the security restriction by,
 *   for example, creating a symbolic link within the sandbox directory that
 *   points to the root folder.  Implementations can avoid this loophole by
 *   converting the file and directory names to absolute paths and resolving
 *   all symbolic links and relative notation before comparing the paths.
 */
// (lethe, sad, ntts use this function)
int
os_is_file_in_dir( const char* filename, const char* path, int include_subdirs,
				  int match_self )
{
	// Essentially a copy of that in frobtads osportable.cc:

	XTOSIFC_DEF_SELNAME(@"os_is_file_in_dir");
	
	NSString *ocFileName = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(filename);
	NSString *ocPath = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(path);
	
	XT_TRACE_4(@"filename=\"%@\" path=\"%@\" include_subdirs=%d match_self=%d", ocFileName, ocPath, include_subdirs, match_self);
	
	char filename_buf[OSFNMAX], path_buf[OSFNMAX];
	size_t flen, plen;
	
	// Absolute-ize the filename, if necessary.
	if (! os_is_file_absolute(filename)) {
		os_get_abs_filename(filename_buf, sizeof(filename_buf), filename);
		filename = filename_buf;
	}
	
	// Absolute-ize the path, if necessary.
	if (! os_is_file_absolute(path)) {
		os_get_abs_filename(path_buf, sizeof(path_buf), path);
		path = path_buf;
	}
	
	// Canonicalize the paths, to remove .. and . elements - this will make
	// it possible to directly compare the path strings.  Also resolve it
	// to the extent possible, to make sure we're not fooled by symbolic
	// links.
	safe_strcpy(filename_buf, sizeof(filename_buf), filename);
	canonicalize_path(filename_buf);
	resolve_path(filename_buf, sizeof(filename_buf), filename_buf);
	filename = filename_buf;
	
	safe_strcpy(path_buf, sizeof(path_buf), path);
	canonicalize_path(path_buf);
	resolve_path(path_buf, sizeof(path_buf), path_buf);
	path = path_buf;
	
	// Get the length of the filename and the length of the path.
	flen = strlen(filename);
	plen = strlen(path);
	
	// If the path ends in a separator character, ignore that.
	if (plen > 0 && path[plen-1] == '/')
		--plen;
	
	// if the names match, return true if and only if we're matching the
	// directory to itself
	if (plen == flen && memcmp(filename, path, flen) == 0) {
		XT_TRACE_1(@"-> %d (plen == flen && memcmp(filename, path, flen) == 0)", match_self);
		return match_self;
	}
	
	// Check that the filename has 'path' as its path prefix.  First, check
	// that the leading substring of the filename matches 'path', ignoring
	// case.  Note that we need the filename to be at least two characters
	// longer than the path: it must have a path separator after the path
	// name, and at least one character for a filename past that.
	if (flen < plen + 2 || memcmp(filename, path, plen) != 0) {
		XT_TRACE_1(@"-> %d (flen < plen + 2 || memcmp(filename, path, plen) != 0)", 0);
		return 0;
	}
	
	// Okay, 'path' is the leading substring of 'filename'; next make sure
	// that this prefix actually ends at a path separator character in the
	// filename.  (This is necessary so that we don't confuse "c:\a\b.txt"
	// as matching "c:\abc\d.txt" - if we only matched the "c:\a" prefix,
	// we'd miss the fact that the file is actually in directory "c:\abc",
	// not "c:\a".)
	if (filename[plen] != '/') {
		XT_TRACE_1(@"-> %d (filename[plen] != '/')", 0);
		return 0;
	}
	
	// We're good on the path prefix - we definitely have a file that's
	// within the 'path' directory or one of its subdirectories.  If we're
	// allowed to match on subdirectories, we already have our answer
	// (true).  If we're not allowed to match subdirectories, we still have
	// one more check, which is that the rest of the filename is free of
	// path separator charactres.  If it is, we have a file that's directly
	// in the 'path' directory; otherwise it's in a subdirectory of 'path'
	// and thus isn't a match.
	if (include_subdirs) {
		// Filename is in the 'path' directory or one of its
		// subdirectories, and we're allowed to match on subdirectories, so
		// we have a match.
		XT_TRACE_1(@"-> %d (include_subdirs)", 1);
		return 1;
	}
	
	// We're not allowed to match subdirectories, so scan the rest of
	// the filename for path separators.  If we find any, the file is
	// in a subdirectory of 'path' rather than directly in 'path'
	// itself, so it's not a match.  If we don't find any separators,
	// we have a file directly in 'path', so it's a match.
	const char* p;
	for (p = filename; *p != '\0' && *p != '/' ; ++p)
		;
	
	// If we reached the end of the string without finding a path
	// separator character, it's a match .
	int res = (*p == '\0');
	XT_TRACE_1(@"-> %d (final)", res);
	return res;
}

/* Resolve symbolic links in a path.  It's okay for 'buf' and 'path'
 * to point to the same buffer if you wish to resolve a path in place.
 */
void
resolve_path( char *buf, size_t buflen, const char *path )
{
	// Basically a copy of that in frobtads osportable.cc:
	
	// Starting with the full path string, try resolving the path with
	// realpath().  The tricky bit is that realpath() will fail if any
	// component of the path doesn't exist, but we need to resolve paths
	// for prospective filenames, such as files or directories we're
	// about to create.  So if realpath() fails, remove the last path
	// component and try again with the remainder.  Repeat until we
	// can resolve a real path, or run out of components to remove.
	// The point of this algorithm is that it will resolve as much of
	// the path as actually exists in the file system, ensuring that
	// we resolve any links that affect the path.  Any portion of the
	// path that doesn't exist obviously can't refer to a link, so it
	// will be taken literally.  Once we've resolved the longest prefix,
	// tack the stripped portion back on to form the fully resolved
	// path.
	
	// make a writable copy of the path to work with
	size_t pathl = strlen(path);
	//char *mypath = new char[pathl + 1];
	char *mypath = calloc(pathl + 1, sizeof(char));
	memcpy(mypath, path, pathl + 1);
	
	// start at the very end of the path, with no stripped suffix yet
	char *suffix = mypath + pathl;
	char sl = '\0';
	
	// keep going until we resolve something or run out of path
	for (;;)
	{
		// resolve the current prefix, allocating the result
		char *rpath = realpath(mypath, 0);
		
		// un-split the path
		*suffix = sl;
		
		// if we resolved the prefix, return the result
		if (rpath != 0)
		{
			// success - if we separated a suffix, reattach it
			if (*suffix != '\0')
			{
				// reattach the suffix (the part after the '/')
				for ( ; *suffix == '/' ; ++suffix) ;
				os_build_full_path(buf, buflen, rpath, suffix);
			}
			else
			{
				// no suffix, so we resolved the entire path
				safe_strcpy(buf, buflen, rpath);
			}
			
			// done with the resolved path
			free(rpath);
			
			// ...and done searching
			break;
		}
		
		// no luck with realpath(); search for the '/' at the end of the
		// previous component in the path
		for ( ; suffix > mypath && *(suffix-1) != '/' ; --suffix) ;
		
		// skip any redundant slashes
		for ( ; suffix > mypath && *(suffix-1) == '/' ; --suffix) ;
		
		// if we're at the root element, we're out of path elements
		if (suffix == mypath)
		{
			// we can't resolve any part of the path, so just return the
			// original path unchanged
			safe_strcpy(buf, buflen, mypath);
			break;
		}
		
		// split the path here into prefix and suffix, and try again
		sl = *suffix;
		*suffix = '\0';
	}
	
	// done with our writable copy of the path
	free(mypath);
}

/*
 *   Get a list of root directories.  If 'buf' is non-null, fills in 'buf'
 *   with a list of strings giving the root directories for the local,
 *   file-oriented devices on the system.  The strings are each null
 *   terminated and are arranged consecutively in the buffer, with an extra
 *   null terminator after the last string to mark the end of the list.
 *
 *   The return value is the length of the buffer required to hold the
 *   results.  If the caller's buffer is null or is too short, the routine
 *   should return the full length required, and leaves the contents of the
 *   buffer undefined; the caller shouldn't expect any contents to be filled
 *   in if the return value is greater than buflen.  Both 'buflen' and the
 *   return value include the null terminators, including the extra null
 *   terminator at the end of the list.  If an error occurs, or the system
 *   has no concept of a root directory, returns zero.
 *
 *   Each result string should be expressed using the syntax for the root
 *   directory on a device.  For example, on Windows, "C:\" represents the
 *   root directory on the C: drive.
 *
 *   "Local" means a device is mounted locally, as opposed to being merely
 *   visible on the network via some remote node syntax; e.g., on Windows
 *   this wouldn't include any UNC-style \\SERVER\SHARE names, and on VMS it
 *   excludes any SERVER:: nodes.  It's up to each system how to treat
 *   virtual local devices, i.e., those that look synctactically like local
 *   devices but are actually mounted network devices, such as Windows mapped
 *   network drives; we recommend including them if it would take extra work
 *   to filter them out, and excluding them if it would take extra work to
 *   include them.  "File-oriented" means that the returned devices are
 *   accessed via file systems, not as character devices or raw block
 *   devices; so this would exclude /dev/xxx devices on Unix and things like
 *   CON: and LPT1: on Windows.
 *
 *   Examples ("." represents a null byte):
 *
 *   Windows: C:\.D:\.E:\..
 *
 *   Unix example: /..
 */
size_t os_get_root_dirs( char* buf, size_t buflen )
{
	XTOSIFC_TRACE_ENTRY(@"os_get_root_dirs");
	
	char *dirs = "/\0\0";
	size_t dirslen = strlen(dirs) + 2;
	
	if (buf != NULL && buflen >= dirslen) {
		strcpy(buf, dirs);
	}
	
	return dirslen;
}

/*
 *   Get a special directory path.  Returns the selected path, in a format
 *   suitable for use with os_build_full_path().  The main program's argv[0]
 *   parameter is provided so that the system code can choose to make the
 *   special paths relative to the program install directory, but this is
 *   entirely up to the system implementation, so the argv[0] parameter can
 *   be ignored if it is not needed.
 *
 *   The 'id' parameter selects which special path is requested; this is one
 *   of the constants defined below.  If the id is not understood, there is
 *   no way of signalling an error to the caller; this routine can fail with
 *   an assert() in such cases, because it indicates that the OS layer code
 *   is out of date with respect to the calling code.
 *
 *   This routine can be implemented using one of the strategies below, or a
 *   combination of these.  These are merely suggestions, though, and systems
 *   are free to ignore these and implement this routine using whatever
 *   scheme is the best fit to local conventions.
 *
 *   - Relative to argv[0].  Some systems use this approach because it keeps
 *   all of the TADS files together in a single install directory tree, and
 *   doesn't require any extra configuration information to find the install
 *   directory.  Since we base the path name on the executable that's
 *   actually running, we don't need any environment variables or parameter
 *   files or registry entries to know where to look for related files.
 *
 *   - Environment variables or local equivalent.  On some systems, it is
 *   conventional to set some form of global system parameter (environment
 *   variables on Unix, for example) for this sort of install configuration
 *   data.  In these cases, this routine can look up the appropriate
 *   configuration variables in the system environment.
 *
 *   - Hard-coded paths.  Some systems have universal conventions for the
 *   installation configuration of compiler-like tools, so the paths to our
 *   component files can be hard-coded based on these conventions.
 *
 *   - Hard-coded default paths with environment variable overrides.  Let the
 *   user set environment variables if they want, but use the standard system
 *   paths as hard-coded defaults if the variables aren't set.  This is often
 *   the best choice; users who expect the standard system conventions won't
 *   have to fuss with any manual settings or even be aware of them, while
 *   users who need custom settings aren't stuck with the defaults.
 */
/* T3 games call this */
void
os_get_special_path( char* buf, size_t buflen, const char* argv0, int id )
{
	XTOSIFC_DEF_SELNAME(@"os_get_special_path");
	
	NSString *ocArgv0 = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(argv0);
	XT_TRACE_2(@"argv0=\"%@\" id=\"%d\"", ocArgv0, id);
	
	switch (id) {
		case OS_GSP_T3_RES:
		case OS_GSP_T3_INC:
		case OS_GSP_T3_LIB:
		case OS_GSP_T3_USER_LIBS:
			// We can safely ignore those. They're needed only by the compiler.
			// OS_GSP_T3_RES is only needed by the base code implementation of
			// charmap.cc (tads3/charmap.cpp) which we don't use.
			
			// TODO? QTads: FIXME: We do use tads3/charmap.cpp, so this needs to be handled.
			XT_WARN_1(@"got unknown id %d", id);
			return;
			
		case OS_GSP_T3_APP_DATA:
		case OS_GSP_LOGFILE: {
			// test/data/debuglog.t calls with this arg
			NSURL *url = [XTFileUtils urlForApplicationSupportDirectory];
			if (url != nil) {
				const char* str = url.fileSystemRepresentation;
				if (strlen(str) < buflen) {
					strcpy(buf, str);
					XT_TRACE_1(@"-> \"%s\"", buf);
				} else {
					XT_ERROR_1(@"buf too short for \"%s\"", str);
				}
			}
			break;
		}
			
		default:
			// We didn't recognize the specified id. That means the base code
			// added a new value for it that we don't know about.
			// TODO? Error dialog.
			XT_WARN_1(@"got unknown id %d", id);
			break;
	}
}


/****************************************************************************
 *  Game modes / text output
 */

/*
 *   Enter HTML mode
 */
void os_start_html()
{
	XTOSIFC_TRACE_ENTRY(@"os_start_html");
	
	[getGameRunner() setHtmlMode:YES];
}

/*
 *   exit HTML mode
 */
void os_end_html()
{
	XTOSIFC_TRACE_ENTRY(@"os_end_html");

	[getGameRunner() setHtmlMode:NO];
}

/*
 *   get the status line mode
 */
int os_get_status()
{
	XTOSIFC_DEF_SELNAME(@"os_get_status");

	int res = (int) getGameRunner().statusLineMode;
	XT_TRACE_1(@"--> %d", res);
	
	return res;
}

/*
 *   Set the status line mode.  There are three possible settings:
 *
 *   0 -> main text mode.  In this mode, all subsequent text written with
 *   os_print() and os_printz() is to be displayed to the main text area.
 *   This is the normal mode that should be in effect initially.  This mode
 *   stays in effect until an explicit call to os_status().
 *
 *   1 -> statusline mode.  In this mode, text written with os_print() and
 *   os_printz() is written to the status line, which is usually rendered as
 *   a one-line area across the top of the terminal screen or application
 *   window.  In statusline mode, leading newlines ('\n' characters) are to
 *   be ignored, and any newline following any other character must change
 *   the mode to 2, as though os_status(2) had been called.
 *
 *   2 -> suppress mode.  In this mode, all text written with os_print() and
 *   os_printz() must simply be ignored, and not displayed at all.  This mode
 *   stays in effect until an explicit call to os_status().
 */
void os_status(int stat)
{
	XTOSIFC_DEF_SELNAME(@"os_status");
	XT_TRACE_1(@"stat=%d", stat);

	/* see what mode we're setting */
	switch(stat)
	{
		case 0:
		default:
			/* turn off status line mode */
			getGameRunner().statusLineMode = STATUS_LINE_MODE_MAIN;
			break;
			
		case 1:
			/* turn on status line mode */
			getGameRunner().statusLineMode = STATUS_LINE_MODE_STATUS;
			break;
			
		case 2:
			getGameRunner().statusLineMode = STATUS_LINE_MODE_SUPPRESS;
			break;
	}
}

/*
 *   clear the screen
 */
void oscls()
{
	XTOSIFC_TRACE_ENTRY(@"oscls");
	
	[getGameRunner() clearScreen];
}

/*
 *   Set the game title.  The output layer calls this routine when a game
 *   sets its title (via an HTML <title> tag, for example).  If it's
 *   convenient to do so, the OS layer can use this string to set a window
 *   caption, or whatever else makes sense on each system.  Most
 *   character-mode implementations will provide an empty implementation,
 *   since there's not usually any standard way to show the current
 *   application title on a character-mode display.
 */
// amoi.gam calls this(?)
void os_set_title(const char *title)
{
	NSString *s = [getGameRunner() makeString:title];
	[getGameRunner() setGameTitle:s];
}

/*
 *   Set text attributes.  We can ignore these if we're in HTML mode,
 *   because we can count on the caller sending us HTML markups directly.
 *   When we're not in HTML mode, though, we need to supply the appropriate
 *   formatting.
 */
void os_set_text_attr(int attrs)
{
	XTOSIFC_DEF_SELNAME(@"os_set_text_attr");
	XT_TRACE_1(@"%lu", attrs);
	
    if (getGameRunner().htmlMode)
        return;
	
	// Impl here is like the htmltads one, where only OS_ATTR_HILITE is considered:
	BOOL hiliteMode = ((attrs & OS_ATTR_HILITE) != 0);
	[getGameRunner() setHiliteMode:hiliteMode];
}

/*
 *   set the score value
 */
void os_score(int cur, int turncount)
{
	XTOSIFC_DEF_SELNAME(@"os_score");
	XT_TRACE_2(@"%d %d", cur, turncount);

	NSString *scoreString = [NSString stringWithFormat:@"%d/%d", cur, turncount];
	[getGameRunner() showScore:scoreString];
}

/*
 *   display a string in the score area in the status line
 */
void os_strsc(const char *p)
{
	XTOSIFC_DEF_SELNAME(@"os_strsc");
	XT_TRACE_1(@"\"%s\"", p);
	
	NSString *scoreString = [getGameRunner() makeString:p];
	[getGameRunner() showScore:scoreString];
}

/*
 *   use plain ascii mode for the display
 */
void os_plain()
{
	//LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_plain");
	/* ignore this -- we can only use the HTML mode */
}

/*
 *   Set non-stop mode.  This tells the OS layer that it should disable any
 *   MORE prompting it would normally do.
 *
 *   This routine is needed only when the OS layer handles MORE prompting; on
 *   character-mode platforms, where the prompting is handled in the portable
 *   console layer, this can be a dummy implementation.
 */
void os_nonstop_mode(int flag)
{
	XTOSIFC_DEF_SELNAME(@"os_nonstop_mode");
	XT_TRACE_1(@"\"%d\"", flag);

	[getGameRunner() setNonstopMode:flag];
}

void os_print(const char *str, size_t len) // str may not end with a \0
{
	if (XT_TRACE_ON) {
		XTOSIFC_DEF_SELNAME(@"os_print");
		char *buf = malloc(len * sizeof(char) + 1); // +1 for \0
		strncpy(buf, str, len);
		buf[len] = '\0';
		XT_TRACE_1(@"str=\"%s\"", buf);
		free(buf);
	}
	
	NSString *s = [getGameRunner() makeString:str len:len];
	[getGameRunner() printOutput:s];
}

/*
 *   write a string
 */
void os_printz(const char *str)
{
    os_print(str, strlen(str));
}

/*
 *   flush any buffered display output
 */
void os_flush()
{
	XTOSIFC_TRACE_ENTRY(@"os_flush");
	
	[getGameRunner() flushOutput];
}

/*
 *   Update the display - process any pending drawing immediately.  This
 *   only needs to be implemented for operating systems that use
 *   event-driven drawing based on window invalidations; the Windows and
 *   Macintosh GUI's both use this method for drawing window contents.
 *
 *   The purpose of this routine is to refresh the display prior to a
 *   potentially long-running computation, to avoid the appearance that the
 *   application is frozen during the computation delay.
 *
 *   Platforms that don't need to process events in the main thread in order
 *   to draw their window contents do not need to do anything here.  In
 *   particular, text-mode implementations generally don't need to implement
 *   this routine.
 *
 *   This routine doesn't absolutely need a non-empty implementation on any
 *   platform, but it will provide better visual feedback if implemented for
 *   those platforms that do use event-driven drawing.
 */
void os_update_display()
{
	//LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_update_display");
}

/****************************************************************************
 *  Game input events
 */

/*
 *   Read a string of input.  Fills in the buffer with a null-terminated
 *   string containing a line of text read from the standard input.  The
 *   returned string should NOT contain a trailing newline sequence.  On
 *   success, returns 'buf'; on failure, including end of file, returns a
 *   null pointer.
 */
unsigned char *os_gets(unsigned char *buf, size_t bufl)
{
	XTOSIFC_DEF_SELNAME(@"os_gets");
	XT_TRACE_1(@"bufl=%lu", bufl);
	
	NSString *command = [getGameRunner() waitForCommand];
	
	if (command == nil) {
		return nil;
	}

	const char* commandCStr = [getGameRunner() makeCStringInteractive:command];
	strncpy((char *)buf, commandCStr, bufl);
	buf[bufl - 1] = 0;
	
	return buf;
}

/*
 The new functions os_gets_timeout() and os_gets_cancel() provides an
 event-oriented interface for reading a command line from the keyboard.
 
 This function is similar to the regular os_gets(), but has a timeout
 feature that allows the keyboard read to be interrupted after a
 specified interval has elapsed, and then possibly resumed later.
 Timed keyboard input is optional; if it can't be implemented on a
 platform, simply provide an implementation for os_gets_timeout() that
 returns OS_EVT_NOTIMEOUT when a timeout is provided, and an empty
 implementation for os_gets_cancel()..
 
 Note that even if timed input
 is not allowed, os_gets_timeout() should minimally call the plain
 os_gets() when the caller does not specify a timeout, and only return
 the OS_EVT_NOTIMEOUT error when a timeout is actually requested by the
 caller.
 */

int os_gets_timeout(unsigned char *buf, size_t bufl,
					unsigned long timeout, int use_timeout)
{
	XTOSIFC_DEF_SELNAME(@"os_gets_timeout");
	XT_TRACE_2(@"timeout=\"%lu\" use_timeout=\"%d\"", timeout, use_timeout);
	
	if (! use_timeout) {
		//unsigned char *os_gets(unsigned char *buf, size_t bufl)
		unsigned char* s = os_gets(buf, bufl);
		if (s != nil) {
			XT_TRACE_0(@"-> OS_EVT_LINE");
			return OS_EVT_LINE;
		} else {
			XT_TRACE_0(@"-> OS_EVT_EOF");
			return OS_EVT_EOF;
		}
	} else {
		//TODO? consider implementing
		XT_TRACE_0(@"-> OS_EVT_NOTIMEOUT");
		return OS_EVT_NOTIMEOUT;
	}
}

void os_gets_cancel(int reset)
{
	//TODO? consider implementing
	//LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_gets_cancel");
}

/*
 *   wait for a character to become available from the keyboard
 */
void os_waitc()
{
	XTOSIFC_TRACE_ENTRY(@"os_waitc");
	
	[getGameRunner() showMorePromptAndWaitForKeyPressed];
}

/*
 *   show the MORE prompt and wait for the user to respond
 */
void os_more_prompt()
{
	XTOSIFC_TRACE_ENTRY(@"os_more_prompt");
	
	[getGameRunner() showMorePromptAndWaitForKeyPressed];
}

/*
 *   Read a character from the keyboard, following the same protocol as
 *   os_getc() for CMD_xxx codes (i.e., when an extended keystroke is
 *   encountered, os_getc_raw() returns zero, then returns the CMD_xxx code
 *   on the subsequent call).
 *
 *   This function differs from os_getc() in that this function returns the
 *   low-level, untranslated key code whenever possible.  This means that,
 *   when a functional interpretation of a key and the raw key-cap
 *   interpretation both exist as CMD_xxx codes, this function returns the
 *   key-cap interpretation.  For the Unix Ctrl-E example in the comments
 *   describing os_getc() above, this function should return 5 (the ASCII
 *   code for Ctrl-E), because the ASCII control character interpretation is
 *   the low-level key code.
 *
 *   This function should return all control keys using their ASCII control
 *   codes, whenever possible.  Similarly, this function should return ASCII
 *   27 for the Escape key, if possible.
 *
 *   For keys for which there is no portable ASCII representation, this
 *   should return the CMD_xxx sequence.  So, this function acts exactly the
 *   same as os_getc() for arrow keys, function keys, and other special keys
 *   that have no ASCII representation.  This function returns a
 *   non-translated version ONLY when an ASCII representation exists - in
 *   practice, this means that this function and os_getc() vary only for CTRL
 *   keys and Escape.
 */
/* QTADS:
 *   Read a character from the keyboard in raw mode.  Because the OS code
 *   handles command line editing itself, we presume that os_getc() is
 *   already returning the low-level raw keystroke information, so we'll
 *   just return that same information.
 */
/* TADS2 portnote:
 The purpose of os_getc_raw() is to allow lower-level access to keystroke
 information, particularly for the inputkey() TADS function, while still
 preserving the port-specific functional mapping that os_getc() affords.
 */
// inkey.t test game calls this
int os_getc_raw()
{
	//XTOSIFC_WARN_ENTRY(@"os_getc_raw");
	
	int res = os_getc_internal();
	
	//XT_TRACE_1(@"-> %d", res);
	
	return res;
}

/*
 *   Read a character from the keyboard.  For extended keystrokes, this
 *   function returns zero, and then returns the CMD_xxx code for the
 *   extended keystroke on the next call.  For example, if the user presses
 *   the up-arrow key, the first call to os_getc() should return 0, and the
 *   next call should return CMD_UP.  Refer to the CMD_xxx codes below.
 *
 *   os_getc() should return a high-level, translated command code for
 *   command editing.  This means that, where a functional interpretation of
 *   a key and the raw key-cap interpretation both exist as CMD_xxx codes,
 *   the functional interpretation should be returned.  For example, on Unix,
 *   Ctrl-E is conventionally used in command editing to move to the end of
 *   the line, following Emacs key bindings.  Hence, os_getc() should return
 *   CMD_END for this keystroke, rather than a single 0x05 character (ASCII
 *   Ctrl-E), because CMD_END is the high-level command code for the
 *   operation.
 *
 *   The translation ability of this function allows for system-dependent key
 *   mappings to functional meanings.
 */
int os_getc() // berrosts.gam calls this
{
	XTOSIFC_TRACE_ENTRY(@"os_getc");
	
	int res = os_getc_internal();
	
	XT_TRACE_1(@"-> %d", res);
	
	return res;
}

int os_getc_internal()
{
	XTOSIFC_TRACE_ENTRY(@"os_getc_internal");
	
	NSUInteger keyPressed = [getGameRunner() waitForAnyKeyPressed];
	
	if (keyPressed == NSUIntegerMax) {
		// couldn't get a key
		static BOOL sendEofCmd = NO;
		int res = (sendEofCmd ? CMD_EOF : 0);
		sendEofCmd = (! sendEofCmd);
		XT_TRACE_1(@"-> (CMD_EOF/0) %d", res);
		return res;
	}
	
	XT_TRACE_1(@"-> %lu", keyPressed);
	
	return (int)keyPressed;
}

/*
 *   Get an input event.  The event types are shown above.  If use_timeout is
 *   false, this routine should simply wait until one of the events it
 *   recognizes occurs, then return the appropriate information on the event.
 *   If use_timeout is true, this routine should return OS_EVT_TIMEOUT after
 *   the given number of milliseconds elapses if no event occurs first.
 *
 *   This function is not obligated to obey the timeout.  If a timeout is
 *   specified and it is not possible to obey the timeout, the function
 *   should simply return OS_EVT_NOTIMEOUT.  The trivial implementation thus
 *   checks for a timeout, returns an error if specified, and otherwise
 *   simply waits for the user to press a key.
 *
 *   A timeout value of 0 does *not* mean that there's no timeout (i.e., it
 *   doesn't mean we should wait indefinitely) - that's specified by passing
 *   FALSE for use_timeout.  A zero timeout also doesn't meant that the
 *   function should unconditionally return OS_EVT_TIMEOUT.  Instead, a zero
 *   timeout specifically means that IF an event is available IMMEDIATELY,
 *   without blocking the thread, we should return that event; otherwise we
 *   should immediately return a timeout event.
 */
int os_get_event(unsigned long timeout_in_milliseconds, int use_timeout, os_event_info_t *info)
{
	XTOSIFC_TRACE_ENTRY(@"os_get_event");
	
	if (use_timeout) {
		//TODO revisit when supporting timed i/o
		//XT_TRACE_0(@"-> OS_EVT_NOTIMEOUT");
		//return OS_EVT_NOTIMEOUT;
	}

	XTGameInputEvent *event = [XTGameInputEvent new];
	[getGameRunner() waitForEvent:event];
	
	int eventType = (int)event.type;
	switch (eventType) {
		case OS_EVT_KEY:
			info->key[0] = (int)event.key0;
			info->key[1] = (int)event.key1;
			if (info->key[0] == NSUIntegerMax) {
				eventType = OS_EVT_EOF;
			}
			break;
		case OS_EVT_HREF: {
			if (event.href != nil) {
				const char* ctsring = [event.href UTF8String];
				strncpy(info->href, ctsring, sizeof(info->href) - 1);
			} else {
				strncpy(info->href, "", 2);
			}
			break;
		}
		case OS_EVT_NONE:
		case OS_EVT_EOF:
			// do nothing
			break;
		default:
			XT_WARN_1(@"unknown/unsupported event type %d", eventType);
			break;
	}
	
	XT_TRACE_1(@"-> %d", eventType);
	return eventType;
}

/*
 *   Check for user break ("control-C", etc) - returns true if a break is
 *   pending, false if not.  If this returns true, it should "consume" the
 *   pending break (probably by simply clearing the OS code's internal
 *   break-pending flag).
 */
int os_break()
{
	// Not relevant on modern OSs
	
	return 0;
}

/*
 *   Ask for input through a dialog.
 *
 *   'prompt' is a text string to display as a prompting message.  For
 *   graphical systems, this message should be displayed in the dialog;
 *   for text systems, this should be displayed on the terminal after a
 *   newline.
 *
 *   'standard_button_set' is one of the OS_INDLG_xxx values defined
 *   below, or zero.  If this value is zero, no standard button set is to
 *   be used; the custom set of buttons defined in 'buttons' is to be used
 *   instead.  If this value is non-zero, the appropriate set of standard
 *   buttons, with labels translated to the local language if possible, is
 *   to be used.
 *     // OK
 *     #define OS_INDLG_OK            1
 *     // OK, Cancel
 *     #define OS_INDLG_OKCANCEL      2
 *     // Yes, No
 *     #define OS_INDLG_YESNO         3
 *     // Yes, No, Cancel
 *     #define OS_INDLG_YESNOCANCEL   4
 *
 *   'buttons' is an array of strings to use as button labels.
 *   'button_count' gives the number of entries in the 'buttons' array.
 *   'buttons' and 'button_count' are ignored if 'standard_button_set' is
 *   non-zero, since a standard set of buttons is used instead.  If
 *   'buttons' and 'button_count' are to be used, each entry contains the
 *   label of a button to show.
 */
/*
 *   An ampersand ('&') character in a label string indicates that the
 *   next character after the '&' is to be used as the short-cut key for
 *   the button, if supported.  The '&' should NOT be displayed in the
 *   string; instead, the character should be highlighted according to
 *   local system conventions.  On Windows, for example, the short-cut
 *   character should be shown underlined; on a text display, the response
 *   might be shown with the short-cut character enclosed in parentheses.
 *   If there is no local convention for displaying a short-cut character,
 *   then the '&' should simply be removed from the displayed text.
 *
 *   The short-cut key specified by each '&' character should be used in
 *   processing responses.  If the user presses the key corresponding to a
 *   button's short-cut, the effect should be the same as if the user
 *   clicked the button with the mouse.  If local system conventions don't
 *   allow for short-cut keys, any short-cut keys can be ignored.
 *
 *   'default_index' is the 1-based index of the button to use as the
 *   default.  If this value is zero, there is no default response.  If
 *   the user performs the appropriate system-specific action to select
 *   the default response for the dialog, this is the response that is to
 *   be selected.  On Windows, for example, pressing the "Return" key
 *   should select this item.
 *
 *   'cancel_index' is the 1-based index of the button to use as the
 *   cancel response.  If this value is zero, there is no cancel response.
 *   This is the response to be used if the user cancels the dialog using
 *   the appropriate system-specific action.  On Windows, for example,
 *   pressing the "Escape" key should select this item.
 */
/*
 *   icon_id is one of the OS_INDLG_ICON_xxx values defined below.  If
 *   possible, an appropriate icon should be displayed in the dialog.
 *   This can be ignored in text mode, and also in GUI mode if there is no
 *   appropriate system icon.
 *      // no icon
 *      // #define OS_INDLG_ICON_NONE     0
 *      // warning
 *      // #define OS_INDLG_ICON_WARNING  1
 *      // information
 *      // #define OS_INDLG_ICON_INFO     2
 *      // question
 *      // #define OS_INDLG_ICON_QUESTION 3
 *      // error
 *      // #define OS_INDLG_ICON_ERROR    4
 *
 *   The return value is the 1-based index of the response selected.  If
 *   an error occurs, return 0.
 */
// Drool.gam, inkeyext (test game) call this
int
os_input_dialog( int icon_id, const char* prompt, int standard_button_set, const char** buttons,
				int button_count, int default_index, int cancel_index )
{
	XTOSIFC_DEF_SELNAME(@"os_input_dialog");
	XT_TRACE_6(@"icon_id=%d prompt=%s standard_button_set=%d button_count=%d default_index=%d cancel_index=%d",
			   icon_id, prompt, standard_button_set, button_count, default_index, cancel_index);
	
	NSString *title = [getGameRunner() makeString:prompt];
	NSUInteger standardButtonSetId = 0;
		//TODO enum?
	NSMutableArray *customButtomSpecs = [NSMutableArray array];
	
	if (standard_button_set != 0) {
		standardButtonSetId = standard_button_set;
		//TODO? enum
	} else {
		//customButtomSpecs = [NSMutableArray array];
		for (NSUInteger i = 0; i < button_count; i++) {
			NSString *buttonString = [getGameRunner() makeString:buttons[i]];
			[customButtomSpecs addObject:buttonString];
		}
	}
	
	XTadsInputDialogIconId iconId = XTadsInputDialogIconIdNone;
	switch (icon_id) {
		case OS_INDLG_ICON_NONE:
			iconId = XTadsInputDialogIconIdNone;
			break;
		case OS_INDLG_ICON_WARNING:
			iconId = XTadsInputDialogIconIdWarning;
			break;
		case OS_INDLG_ICON_INFO:
			iconId = XTadsInputDialogIconIdInfo;
			break;
		case OS_INDLG_ICON_QUESTION:
			iconId = XTadsInputDialogIconIdQuestion;
			break;
		case OS_INDLG_ICON_ERROR:
			iconId = XTadsInputDialogIconIdError;
			break;
		default:
			XT_WARN_1(@"got unexpected icon_id %d", icon_id);
			break;
	}
	
	NSUInteger resIndex = [getGameRunner() inputDialogWithTitle:title
											standardButtonSetId:standardButtonSetId
											  customButtomSpecs:customButtomSpecs
												   defaultIndex:default_index
													cancelIndex:cancel_index
														 iconId:iconId];
	
	XT_TRACE_1(@"-> %d", (int)resIndex);
	
	return (int)resIndex;
}

/*
 *   Ask the user for a filename, using a system-dependent dialog or other
 *   mechanism.  Returns one of the OS_AFE_xxx status codes (see below).
 *
 *   prompt_type is the type of prompt to provide -- this is one of the
 *   OS_AFP_xxx codes (see below).  The OS implementation doesn't need to
 *   pay any attention to this parameter, but it can be used if desired to
 *   determine the type of dialog to present if the system provides
 *   different types of dialogs for different types of operations.
 *
 *   file_type is one of the OSFTxxx codes for system file type.  The OS
 *   implementation is free to ignore this information, but can use it to
 *   filter the list of files displayed if desired; this can also be used
 *   to apply a default suffix on systems that use suffixes to indicate
 *   file type.  If OSFTUNK is specified, it means that no filtering
 *   should be performed, and no default suffix should be applied.
 */
int
os_askfile( const char* prompt, char* fname_buf, int fname_buf_len, int prompt_type, os_filetype_t file_type )
{
	XTOSIFC_DEF_SELNAME(@"os_askfile");
	XT_TRACE_2(@"prompt_type=%d file_type=%d", prompt_type, file_type);
	
	//TODO consider moving much of this logic to gameRunner impl
	
	NSString *dialogTitle = @"Select File";
	if (prompt != nil) {
		//dialogTitle = XTADS_FILESYSTEM_C_STRING_TO_NSSTRING(prompt);
		dialogTitle = [getGameRunner() makeString:prompt];
	}
	
	NSString *fileTypeDescription = @"(fileTypeDesription not specified)";
	NSArray *allowedExtension = @[];
	
	switch (file_type) {
		case OSFTGAME:
			fileTypeDescription = @"TADS 2 games";
			allowedExtension = @[XT_FILE_EXTENSION_TADS2_GAME];
			break;
		case OSFTSAVE:
			fileTypeDescription = @"TADS 2 saved sames";
			allowedExtension = @[XT_FILE_EXTENSION_TADS2_SAVE];
			break;
		case OSFTLOG:
			fileTypeDescription = @"Game transcripts";
			allowedExtension = @[XT_FILE_EXTENSION_TRANSCRIPT];
			break;
		case OSFTT3IMG:
			fileTypeDescription = @"TADS 3 games";
			allowedExtension = @[XT_FILE_EXTENSION_TADS3_GAME];
			break;
		case OSFTT3SAV:
			fileTypeDescription = @"TADS 3 saved games";
			allowedExtension = @[XT_FILE_EXTENSION_TADS3_SAVE];
			break;
		case OSFTCMD:
			fileTypeDescription = @"Command log files";
			allowedExtension = @[XT_FILE_EXTENSION_COMMAND_LOG];
			break;
		case OSFTUNK:
			// note "save" in cquest.gam, firebird.gam sends this value, don't know why
			fileTypeDescription = nil;
			allowedExtension = nil;
			break;
		default:
			XT_ERROR_1(@"failed due to unknown file_type=%d", file_type);
			return OS_AFE_FAILURE;
	}
	
	BOOL existingFile = (prompt_type == OS_AFP_OPEN);
	
	NSURL *url = nil;
	
	switch (file_type) {
		case OSFTSAVE:
		case OSFTT3SAV:
			// Saved T2/T3 game
			url = [getGameRunner() getFileNameForFor:XTadsFileNameDialogFileTypeSavedGame
										 dialogTitle:dialogTitle
								 fileTypeDescription:fileTypeDescription
								   allowedExtensions:allowedExtension
										existingFile:existingFile];
			break;
		case OSFTLOG:
			// Transcript
			url = [getGameRunner() getFileNameForFor:XTadsFileNameDialogFileTypeTranscript
										 dialogTitle:dialogTitle
								 fileTypeDescription:fileTypeDescription
								   allowedExtensions:allowedExtension
										existingFile:existingFile];
			break;
		case OSFTCMD:
			// Command script
			url = [getGameRunner() getFileNameForFor:XTadsFileNameDialogFileTypeCommandScript
										 dialogTitle:dialogTitle
								 fileTypeDescription:fileTypeDescription
								   allowedExtensions:allowedExtension
										existingFile:existingFile];
			break;
		case OSFTUNK:
			// Unknown
			url = [getGameRunner() getFileNameForFor:XTadsFileNameDialogFileTypeGeneral
										 dialogTitle:dialogTitle
								 fileTypeDescription:fileTypeDescription
								   allowedExtensions:allowedExtension
										existingFile:existingFile];
			break;
		case OSFTGAME:
		case OSFTT3IMG:
			// Game files
			XT_WARN_1(@"unhandled file_type %d", file_type);
			break;
		default:
			XT_WARN_1(@"unknown file_type %d", file_type);
			break;
	}

	if (url == nil) {
		return OS_AFE_CANCEL;
	}
	
	BOOL okUrlString = [url getFileSystemRepresentation:fname_buf maxLength:(NSUInteger)fname_buf_len];
	if (okUrlString) {
		return OS_AFE_SUCCESS;
	} else {
		XT_ERROR_1(@"couldn't convert file url \"%@\"to string", url);
		return OS_AFE_FAILURE;
	}
}

/*
 *   Open a popup menu window.  The new window should be opened at the given
 *   x,y coordinates (given in pixels, relative to the top left of the main
 *   game window), and should be drawn in a style consistent with local
 *   system conventions for context menus.  If 'default_pos' is true, ignore
 *   the x,y coordinates and place the menu at a suitable default position,
 *   according to local conventions; for example, on Windows, the default
 *   position for a context menu is at the current mouse position.  The given
 *   HTML text is to be used as the contents of the new window.  The window
 *   should be sized to fit the contents.
 *
 *   The window should have essentially the same lifetime as a context menu
 *   would have.  That is, if the user clicks the mouse outside of the popup,
 *   the popup should be automatically closed.  If the user clicks on a
 *   hyperlink in the popup, the hyperlink should fire its event as normal,
 *   and the popup window should be closed.
 *
 *   If we successfully show the menu, and the user clicks on a hyperlink
 *   item from the menu, we'll return OSPOP_HREF, and we'll fill in *evt with
 *   the event information for the hyperlink selection.  If we successfully
 *   show the menu, but the user clicks outside the menu or otherwise cancels
 *   the menu without making a selection, we'll return OSPOP_CANCEL.  If we
 *   can't show the menu, we'll return OSPOP_FAIL.  If the application is
 *   shut down while we're showing the menu (because the user closes the app,
 *   for example, or because the system is shutting down) we'll return
 *   OSPOP_EOF.
 */
int
os_show_popup_menu( int default_pos, int x, int y, const char* txt,
				   size_t txtlen, union os_event_info_t* evt )
{
	/*TODO? impl
	 if (qFrame->gameRunning()) {
	 return OSPOP_FAIL;
	 }
	 return OSPOP_EOF;
	 */
	LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_show_popup_menu");
	return 0; // == OSPOP_FAIL
}

/*
 *   Pause prior to exit, if desired.  This is meant to be called by
 *   portable code just before the program is to be terminated; it can be
 *   implemented to show a prompt and wait for user acknowledgment before
 *   proceeding.  This is useful for implementations that are using
 *   something like a character-mode terminal window running on a graphical
 *   operating system: this gives the implementation a chance to pause
 *   before exiting, so that the window doesn't just disappear
 *   unceremoniously.
 *
 *   This is allowed to do nothing at all.  For regular character-mode
 *   systems, this routine usually doesn't do anything, because when the
 *   program exits, the terminal will simply return to the OS command
 *   prompt; none of the text displayed just before the program exited will
 *   be lost, so there's no need for any interactive pause.  Likewise, for
 *   graphical systems where the window will remain open, even after the
 *   program exits, until the user explicitly closes the window, there's no
 *   need to do anything here.
 *
 *   If this is implemented to pause, then this routine MUST show some kind
 *   of prompt to let the user know we're waiting.  In the simple case of a
 *   text-mode terminal window on a graphical OS, this should simply print
 *   out some prompt text ("Press a key to exit...") and then wait for the
 *   user to acknowledge the prompt (by pressing a key, for example).  For
 *   graphical systems, the prompt could be placed in the window's title
 *   bar, or status-bar, or wherever is appropriate for the OS.
 */
void
os_expause( void )
{
	// Nothing
}


/****************************************************************************
 *  Other game support
 */

/* Get system information.
 */
int os_get_sysinfo( int code, void* param, long* result )
{
	XTOSIFC_DEF_SELNAME(@"os_get_sysinfo");
	XT_TRACE_1(@"code=%d", code);
	
    switch(code)
    {
		case SYSINFO_HTML:
			*result = 1;
			break;
		case SYSINFO_JPEG:
		case SYSINFO_PNG:
			*result = 1; //TODO 0? (drool.game breed list prefixes)
			break;
		case SYSINFO_LINKS_HTTP:
		case SYSINFO_LINKS_FTP:
		case SYSINFO_LINKS_NEWS:
		case SYSINFO_LINKS_MAILTO:
		case SYSINFO_LINKS_TELNET:
			*result = 1;
			break;
		case SYSINFO_PNG_TRANS:
		case SYSINFO_PNG_ALPHA:
		case SYSINFO_OGG:
		case SYSINFO_MNG:
		case SYSINFO_MNG_TRANS:
		case SYSINFO_MNG_ALPHA:
			*result = 0;
			break;
		case SYSINFO_TEXT_HILITE:  //TODO check
			// space.t3 uses this
			*result = 1;
			break;
		case SYSINFO_BANNERS:
			*result = 1;
			break;
		case SYSINFO_WAV:
		case SYSINFO_MIDI:
		case SYSINFO_WAV_MIDI_OVL:
		case SYSINFO_WAV_OVL:
		case SYSINFO_MPEG:
		case SYSINFO_MPEG1:
		case SYSINFO_MPEG2:
		case SYSINFO_MPEG3:
		case SYSINFO_AUDIO_FADE:
		case SYSINFO_AUDIO_CROSSFADE:
		case SYSINFO_PREF_IMAGES:
		case SYSINFO_PREF_SOUNDS:
		case SYSINFO_PREF_MUSIC:
			*result = 0;
			break;
		case SYSINFO_PREF_LINKS:
			*result = 1; // displayed normally
			break;
		case SYSINFO_TEXT_COLORS:
			//TODO SYSINFO_TXC_* when allowing games to set text colour
			*result = SYSINFO_TXC_RGB; // blighted isle help uses this (Colouring Unvisited Exits)
			break;
		case SYSINFO_INTERP_CLASS:
			*result = SYSINFO_ICLASS_HTML; //SYSINFO_ICLASS_TEXTGUI; // qtads: SYSINFO_ICLASS_HTML;
			break;
		default:
			XT_ERROR_1(@"unknown code %d", code);
			return 0;
    }
    return 1;
}


/****************************************************************************
 *  Time stuff
 */

/*
 *   Sleep for a given interval.  This should simply pause for the given
 *   number of milliseconds, then return.  On multi-tasking systems, this
 *   should use a system API to suspend the current process for the desired
 *   delay; on single-tasking systems, this can simply sit in a wait loop
 *   until the desired interval has elapsed.
 */
void os_sleep_ms( long ms )
{
	// Note: the timeDelay() T3 function calls this
	
	XTOSIFC_DEF_SELNAME(@"os_sleep_ms");
	XT_TRACE_1(@"ms=%ld", ms);
	
	double seconds = ((double)ms) / 1000.0f;
	
	[getGameRunner() sleepFor:seconds];
}

/*
 *   Get the current system high-precision timer.  This function returns a
 *   value giving the wall-clock ("real") time in milliseconds, relative to
 *   any arbitrary zero point.  It doesn't matter what this value is relative
 *   to -- the only important thing is that the values returned by two
 *   different calls should differ by the number of actual milliseconds that
 *   have elapsed between the two calls.  This might be the number of
 *   milliseconds since the computer was booted, since the current user
 *   logged in, since midnight of the previous night, since the program
 *   started running, since 1-1-1970, etc - it doesn't matter what the epoch
 *   is, so the implementation can use whatever's convenient on the local
 *   system.
 */
long
os_get_sys_clock_ms( void )
{
	XTOSIFC_DEF_SELNAME(@"os_get_sys_clock_ms");
	
	static NSDate *zeroPoint = nil;
	NSDate *now = [NSDate date];
	if (zeroPoint == nil) {
		zeroPoint = now;
	}
	NSTimeInterval secondsElapsed = [now timeIntervalSinceDate:zeroPoint];
	if (secondsElapsed < 0.0) {
		XT_WARN_0(@"secondsElapsed < 0.0");
		secondsElapsed = 0.0;
	}
	double millisecondsElapsed = secondsElapsed * 1000.0;
	long ret = millisecondsElapsed;
	
	XT_TRACE_1(@"-> %ld", ret);
	
	return ret;
}

/*
 *   Higher-precision time.  This retrieves the same time information as
 *   os_time() (i.e., the elapsed time since the standard Unix Epoch, January
 *   1, 1970 at midnight UTC), but retrieves it with the highest precision
 *   available on the local system, up to nanosecond precision.  If less
 *   precision is available, that's fine; just return the time to the best
 *   precision available, but expressed in terms of the number of
 *   nanoseconds.  For example, if you can retrieve milliseconds, you can
 *   convert that to nanoseconds by multiplying by 1,000,000.
 *
 *   On return, fills in '*seconds' with the number of whole seconds since
 *   the Epoch, and fills in '*nanoseconds' with the fractional portion,
 *   expressed in nanosceconds.  Note that '*nanoseconds' is merely the
 *   fractional portion of the time, so 0 <= *nanoseconds < 1000000000.
 */
void
os_time_ns( os_time_t* seconds, long* nanoseconds )
{
	// vmdate.cpp (caldate_t::now) calls this
	
	XTOSIFC_DEF_SELNAME(@"os_time_ns");

	NSDate *now = [NSDate date];
	NSTimeInterval secondsSince1970 = now.timeIntervalSince1970;
	
	NSTimeInterval roundedDownSecondsSince1970 = floor(secondsSince1970);
	
	NSTimeInterval partialSecond = secondsSince1970 - roundedDownSecondsSince1970;
	if (partialSecond < 0.0 || partialSecond >= 1.0) {
		XT_ERROR_1(@"partialSecond == %lf", partialSecond);
	}
	double partialSecondAsNanoSecs = 1000000000.0 * partialSecond;
	if (partialSecondAsNanoSecs < 0.0 || partialSecondAsNanoSecs > 1000000000.0) {
		XT_ERROR_1(@"partialSecondAsNanoSecs == %lf", partialSecondAsNanoSecs);
	}
	
	*seconds = (os_time_t)roundedDownSecondsSince1970;
	*nanoseconds = (long)partialSecondAsNanoSecs;
}


/****************************************************************************
 *  Randomization
 */

/* Get a suitable seed for a random number generator.
 */
void
os_rand( long* val )
{
	XTOSIFC_TRACE_ENTRY(@"os_rand");

	unsigned char* buf = (unsigned char*)val;
	size_t len = sizeof(val);
	xtads_gen_rand_bytes(buf, len);
}

/* Generate random bytes for use in seeding a PRNG (pseudo-random number
 * generator).
 */
void
os_gen_rand_bytes( unsigned char* buf, size_t len )
{
	XTOSIFC_DEF_SELNAME(@"os_gen_rand_bytes");
	XT_TRACE_1(@"len=%d", len);

	xtads_gen_rand_bytes(buf, len);
}
	
void
xtads_gen_rand_bytes( unsigned char* buf, size_t len )
{
	// See https://developer.apple.com/library/mac/documentation/Security/Conceptual/cryptoservices/RandomNumberGenerationAPIs/RandomNumberGenerationAPIs.html#//apple_ref/doc/uid/TP40011172-CH12-SW2
	
	XTOSIFC_DEF_SELNAME(@"xtads_gen_rand_bytes");
	XT_TRACE_1(@"len=%d", len);
	
	const char* devRandom = "/dev/random";
	
	FILE *fp = fopen(devRandom, "r");
	if (fp == NULL) {
		XT_ERROR_1(@"failed to open %s for reading", devRandom);
		return;
	}
	
	int i;
	for (i = 0; i < len; i++) {
		int c = fgetc(fp);
		buf[i] = c;
		//XT_TRACE_1(@"c=%d", c);
	}
	
	fclose(fp);
}


/****************************************************************************
 *  Character mapping / encoding
 */

/*
 *   Generate a filename for a character-set mapping file.  This function
 *   should determine the current native character set in use, if
 *   possible, then generate a filename, according to system-specific
 *   conventions, that we should attempt to load to get a mapping between
 *   the current native character set and the internal character set
 *   identified by 'internal_id'.
 *
 *   The internal character set ID is a string of up to 4 characters.
 *
 *   On DOS, the native character set is a DOS code page.  DOS code pages
 *   are identified by 3- or 4-digit identifiers; for example, code page
 *   437 is the default US ASCII DOS code page.  We generate the
 *   character-set mapping filename by appending the internal character
 *   set identifier to the DOS code page number, then appending ".TCP" to
 *   the result.  So, to map between ISO Latin-1 (internal ID = "La1") and
 *   DOS code page 437, we would generate the filename "437La1.TCP".
 *
 *   Note that this function should do only two things.  First, determine
 *   the current native character set that's in use.  Second, generate a
 *   filename based on the current native code page and the internal ID.
 *   This function is NOT responsible for figuring out the mapping or
 *   anything like that -- it's simply where we generate the correct
 *   filename based on local convention.
 *
 *   'filename' is a buffer of at least OSFNMAX characters.
 *
 *   'argv0' is the executable filename from the original command line.
 *   This parameter is provided so that the system code can look for
 *   mapping files in the original TADS executables directory, if desired.
 */
/* QTads: Generate a filename for a character-set mapping file.
 *
 * Follow DOS convention: start with the current local charset
 * identifier, then the internal ID, and the suffix ".tcp".  No path
 * prefix, which means look in current directory.  This is what we
 * want, because mapping files are supposed to be distributed with a
 * game, not with the interpreter.
 */
void
os_gen_charmap_filename( char* filename, char* internal_id, char* unused)
{
	// Find the bare name of the mapping file.
	// (XTads uses Unicode/UTF-8 internally, but the T2 VM expects a single-byte to single-byte mapping,
	// so provide a mapping file that does nothing. The real decoding is done later.)
	
	char tempFilename[OSFNMAX];
	strcat(tempFilename, internal_id);
	strcat(tempFilename, internal_id);
	strcat(tempFilename, ".tcp");
	NSUInteger tempFilenameLen = strlen(tempFilename);
	NSString *tempFilenameNSString = XTADS_FILESYSTEM_C_STRING_LEN_TO_NSSTRING(tempFilename, tempFilenameLen);
	
	// Find the fully qualified name of the mapping file.
	
	NSBundle *mainBundle = [NSBundle mainBundle];
	NSString *mainBundleResourcePath = [mainBundle resourcePath];
	NSString *fqFilename = [NSString stringWithFormat:@"%@/%@", mainBundleResourcePath, tempFilenameNSString];
	
	// Fill in result buffer.

	const char *fqFilenameCString = XTADS_NSSTRING_TO_FILESYSTEM_C_STRING(fqFilename);
	size_t fqFilenameCStringLen = strlen(fqFilenameCString);
	strncpy(filename, fqFilenameCString, fqFilenameCStringLen);
	filename[OSFNMAX - 1] = 0;
}

/*
 *   Generate the name of the character set mapping table for Unicode
 *   characters to and from the given local character set.  Fills in the
 *   buffer with the implementation-dependent name of the desired
 *   character set map.  See below for the character set ID codes.
 *
 *   For example, on Windows, the implementation would obtain the
 *   appropriate active code page (which is simply a Windows character set
 *   identifier number) from the operating system, and build the name of
 *   the Unicode mapping file for that code page, such as "CP1252".  On
 *   Macintosh, the implementation would look up the current script system
 *   and return the name of the Unicode mapping for that script system,
 *   such as "ROMAN" or "CENTEURO".
 *
 *   If it is not possible to determine the specific character set that is
 *   in use, this function should return "asc7dflt" (ASCII 7-bit default)
 *   as the character set identifier on an ASCII system, or an appropriate
 *   base character set name on a non-ASCII system.  "asc7dflt" is the
 *   generic character set mapping for plain ASCII characters.
 *
 *   The given buffer must be at least 32 bytes long; the implementation
 *   must limit the result it stores to 32 bytes.  (We use a fixed-size
 *   buffer in this interface for simplicity, and because there seems no
 *   need for greater flexibility in the interface; a character set name
 *   doesn't carry very much information so shouldn't need to be very
 *   long.  Note that this function doesn't generate a filename, but
 *   simply a mapping name; in practice, a map name will be used to
 *   construct a mapping file name.)
 *
 *   Because this function obtains the Unicode mapping name, there is no
 *   need to specify the internal character set to be used: the internal
 *   character set is Unicode.
 */
/*
 *   Implementation note: when porting this routine, the convention that
 *   you use to name your mapping files is up to you.  You should simply
 *   choose a convention for this implementation, and then use the same
 *   convention for packaging the mapping files for your OS release.  In
 *   most cases, the best convention is to use the names that the Unicode
 *   consortium uses in their published cross-mapping listings, since
 *   these listings can be used as the basis of the mapping files that you
 *   include with your release.  For example, on Windows, the convention
 *   is to use the code page number to construct the map name, as in
 *   CP1252 or CP1250.
 */
/* Generate the name of the character set mapping table for Unicode
 * characters to and from the given local character set.
 *
 * We use UTF-8 for everything, which should work on all platforms.
 */
void
os_get_charmap( char* mapname, int charmap_id )
{
	XTOSIFC_TRACE_ENTRY(@"os_get_charmap");
	
	strcpy(mapname, "utf-8");
}

/*
 *   Receive notification that a character mapping file has been loaded.
 *   The caller doesn't require this routine to do anything at all; this
 *   is purely for the system-dependent code's use so that it can take
 *   care of any initialization that it must do after the caller has
 *   loaded a charater mapping file.  'id' is the character set ID, and
 *   'ldesc' is the display name of the character set.  'sysinfo' is the
 *   extra system information string that is stored in the mapping file;
 *   the interpretation of this information is up to this routine.
 *
 *   For reference, the Windows version uses the extra information as a
 *   code page identifier, and chooses its default font character set to
 *   match the code page.  On DOS, the run-time requires the player to
 *   activate an appropriate code page using a DOS command (MODE CON CP
 *   SELECT) prior to starting the run-time, so this routine doesn't do
 *   anything at all on DOS.
 */
void
os_advise_load_charmap( char* id, char* ldes, char* sysinfo)
{
	NSString *interalCharSetId = [NSString stringWithUTF8String:id];
	[getGameRunner() setTads2InternalCharSet:interalCharSetId];
}

/*
 *   Translate a character from the HTML 4 Unicode character set to the
 *   current character set used for display.  Takes an HTML 4 character
 *   code and returns the appropriate local character code.
 *
 *   The result buffer should be filled in with a null-terminated string
 *   that should be used to represent the character.  Multi-character
 *   results are possible, which may be useful for certain approximations
 *   (such as using "(c)" for the copyright symbol).
 *
 *   Note that we only define this prototype if this symbol isn't already
 *   defined as a macro, which may be the case on some platforms.
 *   Alternatively, if the function is already defined (for example, as an
 *   inline function), the defining code can define OS_XLAT_HTML4_DEFINED,
 *   in which case we'll also omit this prototype.
 *
 *   Important: this routine provides the *default* mapping that is used
 *   when no external character mapping file is present, and for any named
 *   entities not defined in the mapping file.  Any entities in the
 *   mapping file, if used, will override this routine.
 *
 *   A trivial implementation of this routine (that simply returns a
 *   one-character result consisting of the original input character,
 *   truncated to eight bits if necessary) can be used if you want to
 *   require an external mapping file to be used for any game that
 *   includes HTML character entities.  The DOS version implements this
 *   routine so that games will still look reasonable when played with no
 *   mapping file present, but other systems are not required to do this.
 */
// Only used for T2 transcripts.
void
os_xlat_html4(unsigned int html4_char, char *result, size_t result_buf_len)
{
	unichar ch = (unichar)html4_char;
	ch = map_nonlatin1_unicode_char_for_xlat_html4(ch);
	NSString *s = [[NSString alloc] initWithCharacters:&ch length:1];
	const char *tempResult = [getGameRunner() makeCStringQuiet:s];
	if (tempResult == nil) {
		// try ASCII-fying it
		ch = map_nonlatin1_unicode_char_for_xlat_html4(ch);
		NSString *s = [[NSString alloc] initWithCharacters:&ch length:1];
		const char *tempResult = [getGameRunner() makeCStringQuiet:s];
		if (tempResult == nil) {
			// No luck, give up
			tempResult = "?";
		}
	}
	strcpy(result, tempResult);
}

/* 
 *  This is purely to make T2 transcripts look better for Unicode chars that have no ASCII equivalents
 */
unsigned int map_nonlatin1_unicode_char_for_xlat_html4(unsigned int unicode_char)
{
	if (unicode_char > 255)
		{
		switch(unicode_char)
			{
			case 338: unicode_char = 'O'; break;  // Latin capital ligature oe
			case 339: unicode_char = 'o'; break;  // Latin small ligature oe
			case 376: unicode_char = 'Y'; break;  // Latin capital letter y with diaeresis
			case 352: unicode_char = 'S'; break;  // Latin capital letter s with caron
			case 353: unicode_char = 's'; break;  // Latin small letter s with caron
			case 8211: unicode_char = '-'; break; // en dash
			case 8212: unicode_char = '-'; break; // em dash
			case 8216: unicode_char = '\''; break; // left single quotation mark
			case 8217: unicode_char = '\''; break;  // right single quotation mark
			case 8218: unicode_char = '\''; break; // single low-9 quotation mark
			case 8220: unicode_char = '"'; break;  // left double quotation mark
			case 8221: unicode_char = '"'; break;  // right double quotation mark
			case 8222: unicode_char = '"'; break;  // double low-9 quotation mark
			//case 8224: unicode_char = 134; break;  // dagger, obelisk
			//case 8225: unicode_char = 135; break;  // double dagger, double obelisk
			//case 8240: unicode_char = 137; break;  // per mille sign
			case 8249: unicode_char = '\''; break;  // single left-pointing angle quotation mark[
			case 8250: unicode_char = '\''; break;  // single right-pointing angle quotation mark[g]
			//case 8482: unicode_char = 153; break; // trademark symbol
			default: break;
			}
		}
	return unicode_char;
}


/****************************************************************************
 *  Banner API impl.
 */

void *os_banner_create(void *parent, int where, void *other, int wintype,
                       int align, int siz, int siz_units,
                       unsigned long style)
{
	void *res = [getGameRunner() bannerCreate:parent
										where:where
										other:other
									  wintype:wintype
										align:align
										 size:siz
									sizeUnits:siz_units
										style:style];
	return res;
}

void os_banner_delete(void *banner_handle)
{
	[getGameRunner() bannerDelete:banner_handle];
}

void os_banner_orphan(void *banner_handle)
{
	[getGameRunner() bannerOrphan:banner_handle];
}

int os_banner_getinfo(void *banner_handle, os_banner_info_t *info)
{
	BOOL gotInfo = [getGameRunner() bannerInfo:banner_handle info:info];
	return gotInfo;
}

int os_banner_get_charwidth(void *banner_handle)
{
	NSUInteger res = [getGameRunner() bannerWidthInChars:banner_handle];
	return (int)res;
}

int os_banner_get_charheight(void *banner_handle)
{
	NSUInteger res = [getGameRunner() bannerHeightInChars:banner_handle];
	return (int)res;
}

void os_banner_clear(void *banner_handle)
{
	[getGameRunner() bannerClear:banner_handle];
}

void os_banner_disp(void *banner_handle, const char *txt, size_t len)
{
	NSString *s = [getGameRunner() makeString:txt len:len];
	[getGameRunner() bannerDisplay:banner_handle text:s];
}

void os_banner_set_attr(void *banner_handle, int attrs)
{
	//LOG_CALL_TO_UNIMPLEMENTED_FUNCTION(@"os_banner_set_attr");
}

void os_banner_set_color(void *banner_handle, os_color_t fg, os_color_t bg)
{
	[getGameRunner() bannerSetTextColors:banner_handle foregroundColor:fg backgroundColor:bg];
}

void os_banner_set_screen_color(void *banner_handle, os_color_t color)
{
	[getGameRunner() bannerSetScreenColor:banner_handle screenColor:color];
}

void os_banner_flush(void *banner_handle)
{
	[getGameRunner() bannerFlush:banner_handle];
}

void os_banner_set_size(void *banner_handle, int siz, int siz_units,
                        int is_advisory)
{
	[getGameRunner() bannerSetSize:banner_handle size:siz sizeUnits:siz_units isAdvisory:is_advisory];
}

void os_banner_size_to_contents(void *banner_handle)
{
	[getGameRunner() bannerSizeToContents:banner_handle];
}

void os_banner_start_html(void *banner_handle)
{
	[getGameRunner() bannerSetHtmlMode:banner_handle on:YES];
}

void os_banner_end_html(void *banner_handle)
{
	[getGameRunner() bannerSetHtmlMode:banner_handle on:NO];
}

void os_banner_goto(void *banner_handle, int row, int col)
{
	[getGameRunner() bannerGoto:banner_handle row:row column:col];
}

//-----------------------------------

void os_set_text_color(os_color_t c1, os_color_t c2)
{
    /* we ignore this - callers must use HTML tags to set colors */
}

void os_set_screen_color(os_color_t color)
{
    /* we ignore this - callers must use HTML tags to set colors */
}


/****************************************************************************
 *  Low level string / memory functions
 */

/*
 *   Provide memicmp, since it's not a standard libc routine.
 *   (from tads2/msdos/osdosc.c)
 */
int
memicmp( const void* s1, const void* s2, size_t len )
{
	return impl_memicmp((const char*)s1, (const char*)s2, len);
}

int
impl_memicmp(const char* s1, const char* s2, size_t len )
{
	int i;
	
	for (i = 0; i < len; i++)
	{
		if (tolower(s1[i]) != tolower(s2[i]))
			return (int)tolower(s1[i]) - (int)tolower(s2[i]);
	}
	return 0;
}

/* Convert string to all-lowercase.
 */
char* os_strlwr( char* s )
{
	//  assert(s != 0);
	for (int i = 0; s[i] != '\0'; ++i) {
		s[i] = tolower(s[i]);
	}
	return s;
}

int stricmp(const char* str1, const char* str2)
{
	const unsigned char *p1 = (const unsigned char *) str1;
	const unsigned char *p2 = (const unsigned char *) str2;
	int res = 0;
	
	if (p1 == p2) {
		return res;
	}
	
	while (! res) {
		res = tolower(*p1) - tolower(*p2);
		if (*p1++ == '\0') {
			break;
		}
		p2 ++;
	}
	
	return res;
}

int strnicmp (const char *str1, const char *str2, size_t n)
{
	int i, j;
	
	if (! n) {
		return 0;
	}
	
	do
	{
		i = *str1++, j = *str2++;
		i = toupper (i);
		j = toupper (j);
	}
	while (i && i == j && --n);
	
	return (i - j);
}


/****************************************************************************
 *  Unused/obsolete stuff
 */

/*
 *   OBSOLETE - Get filename from startup parameter, if possible; returns
 *   true and fills in the buffer with the parameter filename on success,
 *   false if no parameter file could be found.
 *
 *   (This was used until TADS 2.2.5 for the benefit of the Mac interpreter,
 *   and interpreters on systems with similar desktop shells, to allow the
 *   user to launch the terp by double-clicking on a saved game file.  The
 *   terp would read the launch parameters, discover that a saved game had
 *   been used to invoke it, and would then stash away the saved game info
 *   for later retrieval from this function.  This functionality was replaced
 *   in 2.2.5 with a command-line parameter: the terp now uses the desktop
 *   launch data to synthesize a suitable argv[] vectro to pass to os0main()
 *   or os0main2().  This function should now simply be stubbed out - it
 *   should simply return FALSE.)
 */
int os_paramfile( char* p1)
{
	return 0;
}

/*
 *   Set the default saved-game extension.  This routine will NOT be called
 *   when we're using the standard saved game extension; this routine will be
 *   invoked only if we're running as a stand-alone game, and the game author
 *   specified a non-standard saved-game extension when creating the
 *   stand-alone game.
 *
 *   This routine is not required if the system does not use the standard,
 *   semi-portable os0.c implementation.  Even if the system uses the
 *   standard os0.c implementation, it can provide an empty routine here if
 *   the system code doesn't need to do anything special with this
 *   information.
 *
 *   The extension is specified as a null-terminated string.  The extension
 *   does NOT include the leading period.
 */
void os_set_save_ext( const char* unused)
{
}

/* Yield CPU.
 *
 * We don't need this.  It's only useful for Windows 3.x and maybe pre-X Mac OS
 * and other systems with brain damaged multitasking.
 */
int os_yield( void )
{
	return 0;
}



