intfiction.org

The Interactive Fiction Community Forum
It is currently Sat Dec 16, 2017 12:24 am

All times are UTC - 6 hours [ DST ]




Post new topic Reply to topic  [ 30 posts ]  Go to page Previous  1, 2, 3  Next
Author Message
PostPosted: Thu Sep 21, 2017 4:19 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
I've been working through this....my notes:

The packages.json preinstall script is for BASH. There's an npm library called run-script-os that allows you to define platform specific scripts (preinstall:win32). This statement has to be written for CMD on Windows.

Code:
    "scripts": {
    "start": "electron .",
    "preinstall": "run-script-os",
    "preinstall:macos": "if [ -f quixe/LICENSE ]; then echo Quixe already installed; elif [ -d .git ]; then git submodule init; git submodule update; else git clone https://github.com/erkyrath/quixe.git; fi",
    "preinstall:darwin": "if [ -f quixe/LICENSE ]; then echo Quixe already installed; elif [ -d .git ]; then git submodule init; git submodule update; else git clone https://github.com/erkyrath/quixe.git; fi",
    "preinstall:win32": "if EXISTS quixe/LICENSE ( echo Quixe already installed) else (if EXISTS .git ( git submodule init; git submodule update; ) else (git clone https://github.com/erkyrath/quixe.git; fi)"
  },


This allows npm install to run correctly on windows.

Even so, the makedist.py is clearly meant for a BASH/mac environment as well and so far I can't get it to run on Windows. I've inched closed, but this seems sort of counter-productive since electron has excellent multi-platform build capabilities. (zarf has mentioned he "should" port this to JS, but I assume this is not a priority)

I'm going to dig into this some more and try to get the build working without python.

I assume this works on linux and macos and won't look at those targets.

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Sep 21, 2017 7:10 pm 
Offline

Joined: Sun Oct 11, 2015 5:09 pm
Posts: 276
Yeah, I was able to get npm install to work, eventually, by dropping in the quixe files manually and removing the "if [ -f quixe/LICENSE ];" from the preinstall script (this was craiglocke's solution in this thread.

It's makedist.py that's giving me the problems now.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Sep 21, 2017 8:27 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
Closer. I have dist\win32-* folders created with results. Zip process is last step.

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Sep 21, 2017 8:51 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
I added bestzip and updated win32 rmdir instead of rm.

Now using "makedist.py --game shadow" and realizing --game is never even checked in the program. Adding more platformy things...

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Thu Sep 21, 2017 11:26 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
More work on --game gamedir completed. I have it building into gamedir and copying to dist\gamedir-win32-x64, but the extra files aren't getting copied and not sure yet how to make it default to built-in game.

pycharm is the bomb (JetBrains python IDE with debugger)

I'm pretty close to getting makedist.py work for win32. Of course once I do this, I'll probably just turn this into a gulp task.

But I need a break. Stupid Brewers blew second game in a row...sheesh

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 9:49 am 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
Hey Zarf,

Maybe you can clarify the folder structure for me...

Assume my gamedir is 'shadow'

I have

shadow\files\shadow-2.1.ulx
shadow\files\shadowhints.html
shadow\files\GameFiles\checkmark.png
shadow\files\GameFiles\gearclock.jpg
shadow\files\GameFiles\parchment.jpg
shadow\files\GameFiles\question.jpg

This should get copied to:

lectrote\dist\shadow-win32-x64\resources\app\shadow\files\

Right?

Am I allowed to have a sub-folder in the gamedir?

Or should I flatten all of it into the gamedir?

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 1:10 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
I'm going to conclude that "wrapped" and "unwrapped" are how the zip file is created. "unwrapped" means the zip root is the contents of the dist\win32-ia32 and doesn't actually have the "dist\win32-ia32" as folders within the zip.

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 3:06 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
So I have makefile.py working now for a windows build. Most of the problems were the shell commands that assumed bash was in use instead of cmd on Windows. Then the assumption that a 'zip' command was built-in, though that was an easy fix. Then the copyfile python command on Windows won't overwrite a file and will actually cause an error. Then the build stuff was just weird on Windows. Does like cd %s in subprocess. I had to use os.chdir() and do it once for 'dist' and once for gamedir. Then unwind that with '..\\' twice. Cra-a-azy.

I have Shadow in the Cathedral built, with hints.

If I have time in the near future, all of this really should be moved to gulp tasks. Shouldn't be too hard and the cross-platform stuff would be much cleaner.

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 3:48 pm 
Offline

Joined: Sun Oct 11, 2015 5:09 pm
Posts: 276
That is great news! Is it only a matter of replacing makedist.py with your version, or are there other changes that must be made?


Top
 Profile Send private message  
Reply with quote  
PostPosted: Fri Sep 22, 2017 4:25 pm 
Offline
User avatar

Joined: Thu Feb 11, 2010 1:51 pm
Posts: 746
Location: Chicago, Illinois, USA
That's it, as long as you've removed that preinstall line in package.json.

- Make sure you have your own folder for your story with everything it needs (under the lectrote folder, so lectrote\shadow is what I have)
- Copy the samplegame files to your folder and make changes accordingly (minor HTML edits).
- Update the package.json per lectrote docs (mine is shown below for shadow)
- Make sure you list all extra files (including game file) in lectroteExtraFiles ["file1","file2"]
- npm install --save-dev bestzip
- npm install --save-dev run-script-os (if you decide to use the preinstall scripts)

build first with
py makedist.py win32

then package
py makedist.py --game yourfoldername win32

This will leave zip files in the dist folder, though you can test it by running the compiled elextron app named yourfoldername.exe in the unzipped folder.

Let me know how it goes...everything is below...

Code:
  "scripts": {
    "start": "electron .",
    "preinstall": "run-script-os",
    "preinstall:macos": "if [ -f quixe/LICENSE ]; then echo Quixe already installed; elif [ -d .git ]; then git submodule init; git submodule update; else git clone https://github.com/erkyrath/quixe.git; fi",
    "preinstall:linux": "if [ -f quixe/LICENSE ]; then echo Quixe already installed; elif [ -d .git ]; then git submodule init; git submodule update; else git clone https://github.com/erkyrath/quixe.git; fi",
    "preinstall:win32": "if EXISTS quixe/LICENSE ( echo Quixe already installed) else (if EXISTS .git ( git submodule init; git submodule update; ) else (git clone https://github.com/erkyrath/quixe.git; fi)"
  },


shadow package.json
Code:
{
  "name": "shadow",
  "productName": "shadow",
  "version": "2.1.0",
  "lectroteVersion": "2.8.1",
  "description": "The Shadow in the Cathedral",
  "author": "Ian Finely, Jon Ingold",
  "lectrotePackagedGame": "shadow-2.1.ulx",
  "lectroteSoleInterpreter": "glulx",
  "lectroteExtraFiles": ["shadow-2.1.ulx", "shadowhints.html", "checkmark.png", "gearclock.jpg", "parchment.jpg", "question.jpg",
                          "chapter-1.png", "chapter-2.png", "chapter-3.png", "chapter-4.png", "chapter-5.png", "chapter-6.png",
                          "chapter-7.png", "chapter-8.png", "chapter-9.png", "chapter-10.png", "chapter-11.png" ],
  "lectroteCopyright": "Copyright 2017 by Ian Finley & Jon Ingold",
  "main": "main.js",
  "bin": {
    "lectrote": "./cli.sh"
  },
  "scripts": {
    "start": "electron .",
    "preinstall": "run-script-os",
    "preinstall:macos": "if [ -f quixe/LICENSE ]; then echo Quixe already installed; elif [ -d .git ]; then git submodule init; git submodule update; else git clone https://github.com/erkyrath/quixe.git; fi",
    "preinstall:linux": "if [ -f quixe/LICENSE ]; then echo Quixe already installed; elif [ -d .git ]; then git submodule init; git submodule update; else git clone https://github.com/erkyrath/quixe.git; fi",
    "preinstall:win32": "if EXISTS quixe/LICENSE ( echo Quixe already installed) else (if EXISTS .git ( git submodule init; git submodule update; ) else (git clone https://github.com/erkyrath/quixe.git; fi)"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/erkyrath/lectrote.git"
  },
  "keywords": [
    "interactive fiction",
    "interpreter",
    "quixe",
    "glkote",
    "glulx",
    "parchment",
    "zcode",
    "zmachine",
    "ink"
  ],
  "license": "MIT",
  "dependencies": {
    "electron": "1.6.11"
  },
  "devDependencies": {
    "electron-packager": "^8.0.0",
    "run-script-os": "^1.0.2"
  },
  "optionalDependencies": {
    "appdmg": "^0.4.0"
  }
}


makedist.py
Code:
#!/usr/bin/env python3

# Usage: python3 makedist.py OR for Windows: py makedist.py [--game yourdir] [win32]
#
# This script copies the working files (everything needed to run Lectrote)
# into prebuilt Electron app packages. Fetch these from
#    https://github.com/atom/electron/releases
# and unzip them into a "dist" directory.

import sys
import os, os.path
import optparse
import shutil
import json
import subprocess

all_packages = [
    'darwin-x64',
    'linux-ia32',
    'linux-x64',
    'win32-ia32',
    'win32-x64',
]

popt = optparse.OptionParser()

popt.add_option('-b', '--build',
                action='store_true', dest='makedist',
                help='build dist directories')
popt.add_option('-z', '--zip',
                action='store_true', dest='makezip',
                help='turn dist directories into zip files')
popt.add_option('-n', '--none',
                action='store_true', dest='makenothing',
                help='do nothing except look at the arguments')
popt.add_option('-g', '--game', '--gamedir',
                action='store', dest='gamedir',
                help='directory for game-specific files')
popt.add_option('-v', '--version',
                action='store', dest='buildversion',
                default='1',
                help='build version (default 1)')

(opts, args) = popt.parse_args()


appfiles = [
    './package.json',
    './main.js',
    './apphooks.js',
    './formats.js',
    './play.html',
    './prefs.html',
    './prefs.js',
    './fonts.js',
    './about.html',
    './if-card.html',
    './if-card.js',
    './fonts.css',
    './play.css',
    './el-glkote.css',
    './font',  # all files
    './icon-128.png',
    './docicon-glulx.ico',
    './docicon-zcode.ico',
    './docicon-hugo.ico',
    './docicon-json.ico',
    './quixe/lib/elkote.min.js',
    './quixe/lib/jquery-1.12.4.min.js',
    './quixe/lib/quixe.min.js',
    './quixe/media/waiting.gif',
    {
        'key': 'ifvms',
        'files': [
            './zplay.html',
            './ifvms/zvm.min.js',
            './ifvms/zvm_dispload.min.js',
            './ifvms/zvm.css',
            './ifvms/package.json',
        ]
    },
    {
        'key': 'emglken',
        'files': [
            './emglkenplay.html',
            './emglken/hugo.js',
            './emglken/hugo-core.js.bin',
            './emglken/hugo-core.js.mem',
            './emglken/git.js',
            './emglken/git-core.js.bin',
            './emglken/git-core.js.mem',
            './emglken/emglken_dispload.min.js',
            './emglken/versions.json',
        ]
    },
    {
        'key': 'inkjs',
        'files': [
            './inkplay.html',
            './inkplay.js',
            './inkjs/ink.min.js',
            './inkjs/ink-130.min.js',
            './inkjs/ink-146.min.js',
            './inkjs/package.json',
        ]
    },
]

rootfiles = [
    './LICENSE',   
    './LICENSES-FONTS.txt',
]

win32_rootfiles = [
    'LICENSE',
    'LICENSES-FONTS.txt',
]

def install(resourcedir, pkg):
    if not os.path.isdir(resourcedir):
        raise Exception('path does not exist: ' + resourcedir)
    appdir = resourcedir
    print('Installing to: ' + appdir)

    soleterp = pkg.get('lectroteSoleInterpreter')

    appfilesused = []
    for val in appfiles:
        if type(val) is dict:
            if soleterp and val['key'] != soleterp:
                continue
            for filename in val['files']:
                appfilesused.append(filename)
        else:
            appfilesused.append(val)
   
    os.makedirs(appdir, exist_ok=True)
    qdir = os.path.join(appdir, 'quixe')
    os.makedirs(qdir, exist_ok=True)
    os.makedirs(os.path.join(qdir, 'lib'), exist_ok=True)
    os.makedirs(os.path.join(qdir, 'media'), exist_ok=True)
    zvmdir = os.path.join(appdir, 'ifvms')
    os.makedirs(zvmdir, exist_ok=True)
    emglkendir = os.path.join(appdir, 'emglken')
    os.makedirs(emglkendir, exist_ok=True)
    inkdir = os.path.join(appdir, 'inkjs')
    os.makedirs(inkdir, exist_ok=True)

    for filename in appfilesused:
        srcfilename = filename
        if opts.gamedir:
            val = os.path.join(opts.gamedir, filename)
            if os.path.exists(val):
                srcfilename = val

        tgtfilename = os.path.join(appdir, filename)
        if not os.path.isdir(filename):
            if not os.path.exists(tgtfilename):
                shutil.copyfile(srcfilename, tgtfilename)
        else:
            subdirname = os.path.join(appdir, filename)
            os.makedirs(subdirname, exist_ok=True)
            for subfile in os.listdir(srcfilename):
                tgtfilename = os.path.join(subdirname, subfile)
                if not os.path.exists(tgtfilename):
                    shutil.copyfile(os.path.join(srcfilename, subfile), tgtfilename)

    extrafiles = pkg.get('lectroteExtraFiles')
    if opts.gamedir and extrafiles:
        gamedir = os.path.basename(opts.gamedir)
        os.makedirs(gamedir, exist_ok=True)
        for filename in extrafiles:
            srcfilename = os.path.join(opts.gamedir, filename)
            if not os.path.isdir(filename):
                tgtfilename = os.path.join(gamedir, filename)
                if not os.path.exists(tgtfilename):
                    shutil.copyfile(srcfilename, tgtfilename)
            else:
                subdirname = os.path.join(gamedir, filename)
                os.makedirs(subdirname, exist_ok=True)
                for subfile in os.listdir(srcfilename):
                    tgtfilename = os.path.join(subdirname, subfile)
                    if not os.path.exists(tgtfilename):
                        shutil.copyfile(os.path.join(srcfilename, subfile), tgtfilename)

def builddir(dir, pack, pkg):
    (platform, dummy, arch) = pack.partition('-')

    cmd = 'node_modules/.bin/electron-packager'

    if platform == 'win32':
        cmd = 'node_modules\.bin\electron-packager'

    args = [
        cmd, product_name, product_name,
        '--app-version', product_version,
        '--build-version', opts.buildversion,
        '--arch='+arch, '--platform='+platform,
        '--out', 'dist',
        '--overwrite'
        ]

    if platform == 'darwin':
        appid = 'com.eblong.lectrote'
        if opts.gamedir:
            appid = pkg.get('lectroteMacAppID')
            if not appid:
                raise Exception('Mac package must set lectroteMacAppID')
            if appid == 'com.eblong.lectrote':
                raise Exception('lectroteMacAppID must not be com.eblong.lectrote')

        iconpath = 'resources/appicon-mac.icns'
        if opts.gamedir and os.path.exists(os.path.join(opts.gamedir, 'resources/appicon-mac.icns')):
            iconpath = os.path.join(opts.gamedir, 'resources/appicon-mac.icns')
       
        args = args + [
            '--app-bundle-id='+appid,
            '--app-category-type=public.app-category.games',
            '--icon='+iconpath,
            '--extra-resource=resources/icon-glulx.icns',
            '--extra-resource=resources/icon-zcode.icns',
            '--extra-resource=resources/icon-hugo.icns',
            '--extra-resource=resources/icon-blorb.icns',
            '--extra-resource=resources/icon-gblorb.icns',
            '--extra-resource=resources/icon-zblorb.icns',
            '--extra-resource=resources/icon-glksave.icns',
            '--extra-resource=resources/icon-glkdata.icns',
            '--extra-resource=resources/icon-json.icns',
            '--extend-info', 'resources/Add-Info.plist',
            ]

    if platform == 'win32':
        iconpath = 'resources/appicon-win.ico'
        if opts.gamedir and os.path.exists(os.path.join(opts.gamedir, 'resources/appicon-win.ico')):
            iconpath = os.path.join(opts.gamedir, 'resources/appicon-win.ico')

        filedesc = 'Interactive Fiction Interpreter'
        if opts.gamedir and pkg.get('description'):
            filedesc = pkg.get('description')

        if not opts.gamedir:
            companyname = 'Zarfhome Software'
        else:
            companyname = pkg.get('lectroteCompanyName')
        if companyname:
            args.append('--win32metadata.CompanyName='+companyname)

        if not opts.gamedir:
            copyright = 'Copyright 2016 by Andrew Plotkin'
        else:
            copyright = pkg.get('lectroteCopyright')
        if copyright:
            args.append('--app-copyright='+copyright)
       
        args = args + [
            '--win32metadata.InternalName='+product_name,
            '--win32metadata.ProductName='+product_name,
            '--win32metadata.OriginalFilename='+product_name+'.exe',
            '--win32metadata.FileDescription='+filedesc,
            '--icon='+iconpath,
            ]

        rootfiles = win32_rootfiles
       
    subprocess.call(args, shell=True)

    for filename in rootfiles:
        shutil.copyfile(filename, os.path.join(dir, filename))
    os.unlink(os.path.join(dir, 'version'))
   
def makezip(dir, zipdir, unwrapped=False):
    prefix = product_name + '-'
    val = os.path.split(dir)[-1]
    if not val.startswith(prefix):
        raise Exception('path does not have the prefix')
    zipfile = product_name + '-' + product_version + '-' + val[len(prefix):]
    zipargs = '-q'
    if 'darwin' in zipfile:
        zipfile = zipfile.replace('darwin', 'macos')
        print('AppDMGing up: %s to %s' % (dir, zipfile))
        subprocess.call('rm -f "dist/%s.dmg"; node_modules/.bin/appdmg resources/pack-dmg-spec.json "dist/%s.dmg"' % (zipfile, zipfile),
                        shell=True)
        return
    print('Zipping up: %s to %s (%s)' % (dir, zipfile, ('unwrapped' if unwrapped else 'wrapped')))
    if unwrapped:
        if 'darwin' in zipfile:
            subprocess.call('cd "%s"; rm -f "../%s.zip"; bestzip "%s" -r "../%s.zip" *' % (dir, zipfile, zipargs, zipfile), shell=True)
        if 'win32' in zipfile:
            os.chdir('dist')
            os.chdir(zipdir)
            tgtfile = '..\%s.zip' % (zipfile)
            if os.path.exists(tgtfile):
                os.remove(tgtfile)
            subprocess.call('bestzip ..\%s.zip .\\' % (zipfile), shell=True)
            os.chdir('..\\')
            os.chdir('..\\')
    else:
        dirls = os.path.split(dir)
        subdir = dirls[-1]
        topdir = os.path.join(*os.path.split(dir)[0:-1])

        if 'darwin' in zipfile:
            subprocess.call('cd "%s"; rm -f "%s.zip"; bestzip "%s" -r "%s.zip" "%s"' % (topdir, zipfile, zipargs, zipfile, subdir), shell=True)

        if 'win32' in zipfile:
            subprocess.call('cd "%s"; del /F "%s.zip"; bestzip "%s" -r "%s.zip" "%s"' % (topdir, zipfile, zipargs, zipfile, subdir), shell=True)

# Start work! First, read the version string out of package.json.

pkgfile = 'package.json'
if opts.gamedir and os.path.exists(os.path.join(opts.gamedir, 'package.json')):
    pkgfile = os.path.join(opts.gamedir, 'package.json')
fl = open(pkgfile)
pkg = json.load(fl)
fl.close()

product_version = pkg['version']
product_name = pkg['productName'];
print('%s version: %s' % (product_name, product_version,))
if product_name != 'Lectrote':
    print('%s version: %s' % ('Lectrote', pkg['lectroteVersion'],))

# Decide what distributions we're working on. ("packages" is a bit overloaded,
# sorry.)

packages = []
if not args:
    packages = all_packages
else:
    for pack in all_packages:
        for arg in args:
            if arg in pack:
                packages.append(pack)
                break

if not packages:
    raise Exception('no packages selected')

if not opts.gamedir:
    os.makedirs('tempapp', exist_ok=True)
    install('tempapp', pkg)

if opts.gamedir:
    os.makedirs(opts.gamedir, exist_ok=True)
    install(opts.gamedir, pkg)

os.makedirs('dist', exist_ok=True)

doall = not (opts.makedist or opts.makezip or opts.makenothing)

if doall or opts.makedist:
    for pack in packages:
        (platform, dummy, arch) = pack.partition('-')

        if platform == 'win32':
            dest = 'dist\%s-%s' % (product_name, pack,)
            builddir(dest, pack, pkg)

        if platform == 'darwin':
            dest = 'dist/%s-%s' % (product_name, pack,)
            builddir(dest, pack, pkg)

if doall or opts.makezip:
    for pack in packages:
        (platform, dummy, arch) = pack.partition('-')

        if platform == 'win32':
            zippath = 'dist\%s-%s' % (product_name, pack,)
            zipdir = '%s-%s' % (product_name, pack,)
            makezip(zippath, zipdir, unwrapped=('win32' in pack))

        if platform == 'darwin':
            dest = 'dist/%s-%s' % (product_name, pack,)
            makezip(dest, '', unwrapped=('win32' in pack))

_________________
David C.
http://www.plover.net/~dave/blog
http://www.ifpress.org

"It boots nothing to avoid his snares, for they are ever beset by other snares." - The shade of High Lord Morham, The Wounded Land - Second Chronicles of Thomas Covenant.


Last edited by DavidC on Fri Sep 22, 2017 5:34 pm, edited 1 time in total.

Top
 Profile Send private message  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 30 posts ]  Go to page Previous  1, 2, 3  Next

All times are UTC - 6 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group