Browse Source

Lots of changes to the plugin and pdeploy to make packaged games run without needing write access or internet access

rdb 15 years ago
parent
commit
60973fdb7b

+ 6 - 0
direct/src/p3d/AppRunner.py

@@ -79,6 +79,7 @@ class AppRunner(DirectObject):
     P3DVCNone = 0
     P3DVCNormal = 1
     P3DVCForce = 2
+    P3DVCNever = 3
 
     # Also from p3d_plugin.h
     P3D_CONTENTS_DEFAULT_MAX_AGE = 5
@@ -570,6 +571,11 @@ class AppRunner(DirectObject):
                 totalSize += packageData.totalSize
         self.notify.info("Total Panda3D disk space used: %s MB" % (
             (totalSize + 524288) / 1048576))
+        
+        if self.verifyContents == self.P3DVCNever:
+            # We're not allowed to delete anything anyway.
+            return
+        
         self.notify.info("Configured max usage is: %s MB" % (
             (self.maxDiskUsage + 524288) / 1048576))
         if totalSize <= self.maxDiskUsage:

+ 112 - 60
direct/src/p3d/DeploymentTools.py

@@ -6,7 +6,9 @@ __all__ = ["Standalone", "Installer"]
 
 import os, sys, subprocess, tarfile, shutil, time, zipfile, glob
 from direct.directnotify.DirectNotifyGlobal import *
-from pandac.PandaModules import PandaSystem, HTTPClient, Filename, VirtualFileSystem, Multifile, readXmlStream
+from direct.showbase.AppRunnerGlobal import appRunner
+from pandac.PandaModules import PandaSystem, HTTPClient, Filename, VirtualFileSystem, Multifile
+from pandac.PandaModules import TiXmlDocument, TiXmlDeclaration, TiXmlElement, readXmlStream
 from direct.p3d.HostInfo import HostInfo
 
 class CachedFile:
@@ -27,7 +29,7 @@ class Standalone:
         
         hostDir = Filename(Filename.getTempDirectory(), 'pdeploy/')
         hostDir.makeDir()
-        self.host = HostInfo(PandaSystem.getPackageHostUrl(), appRunner = base.appRunner, hostDir = hostDir, asMirror = False, perPlatform = True)
+        self.host = HostInfo(PandaSystem.getPackageHostUrl(), appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = True)
         
         self.http = HTTPClient.getGlobalPtr()
         if not self.host.hasContentsFile:
@@ -61,6 +63,9 @@ class Standalone:
         
         if platform == None:
             platform = PandaSystem.getPlatform()
+        
+        vfs = VirtualFileSystem.getGlobalPtr()
+        
         for package in self.host.getPackages(name = "p3dembed", platform = platform):
             if not package.downloadDescFile(self.http):
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
@@ -75,7 +80,7 @@ class Standalone:
             else:
                 p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed" % package.platform)
             
-            if not p3dembed.exists():
+            if not vfs.exists(p3dembed):
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
             
@@ -104,7 +109,11 @@ class Standalone:
         output.makeDir()
         ohandle = open(output.toOsSpecific(), "wb")
         ohandle.write(p3dembed_data)
-        for token in self.tokens.items():
+        
+        # Write out the tokens. Set log_basename to the basename by default
+        tokens = {"log_basename" : self.basename}
+        tokens.update(self.tokens)
+        for token in tokens.items():
             ohandle.write("\0%s=%s" % token)
         ohandle.write("\0\0")
         
@@ -134,11 +143,12 @@ class Standalone:
             return []
         
         filenames = []
+        vfs = VirtualFileSystem.getGlobalPtr()
         for e in package.extracts:
             if e.basename not in ["p3dembed", "p3dembed.exe"]:
                 filename = Filename(package.getPackageDir(), e.filename)
                 filename.makeAbsolute()
-                if filename.exists():
+                if vfs.exists(filename):
                     filenames.append(filename)
                 else:
                     Standalone.notify.error("%s mentioned in xml, but does not exist" % e.filename)
@@ -187,14 +197,16 @@ class Installer:
                     self.requirements.append((p3dRequires.Attribute('name'), p3dRequires.Attribute('version')))
                     p3dRequires = p3dRequires.NextSiblingElement('requires')
 
-    def installPackagesInto(self, rootDir, platform):
+    def installPackagesInto(self, hostDir, platform):
         """ Installs the packages required by the .p3d file into
-        the specified root directory, for the given platform. """
+        the specified directory, for the given platform. """
         
         if not self.includeRequires:
             return
         
-        host = HostInfo(self.hostUrl, appRunner = base.appRunner, rootDir = rootDir, asMirror = True, perPlatform = False)
+        packages = []
+        
+        host = HostInfo(self.hostUrl, appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = False)
         if not host.hasContentsFile:
             if not host.readContentsFile():
                 if not host.downloadContentsFile(self.http):
@@ -203,15 +215,17 @@ class Installer:
         
         for name, version in self.requirements:
             package = host.getPackage(name, version, platform)
+            package.installed = True # Hack not to let it install itself
+            packages.append(package)
             if not package.downloadDescFile(self.http):
-                Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
+                Installer.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
             if not package.downloadPackage(self.http):
-                Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
+                Installer.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
         
         # Also install the 'images' package from the same host that p3dembed was downloaded from.
-        host = HostInfo(self.standalone.host.hostUrl, appRunner = base.appRunner, rootDir = rootDir, asMirror = False, perPlatform = False)
+        host = HostInfo(self.standalone.host.hostUrl, appRunner = appRunner, hostDir = hostDir, asMirror = False, perPlatform = False)
         if not host.hasContentsFile:
             if not host.readContentsFile():
                 if not host.downloadContentsFile(self.http):
@@ -219,13 +233,71 @@ class Installer:
                     return
         
         for package in host.getPackages(name = "images"):
+            package.installed = True # Hack not to let it install itself
+            packages.append(package)
             if not package.downloadDescFile(self.http):
-                Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
+                Installer.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
             if not package.downloadPackage(self.http):
-                Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
+                Installer.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
             break
+        
+        # Remove the extracted files from the compressed archive, to save space.
+        vfs = VirtualFileSystem.getGlobalPtr()
+        for package in packages:
+            if package.uncompressedArchive:
+                archive = Filename(package.getPackageDir(), package.uncompressedArchive.filename)
+                if not archive.exists():
+                    continue
+                
+                print archive
+                mf = Multifile()
+                # Make sure that it isn't mounted before altering it, just to be safe
+                vfs.unmount(archive)
+                if not mf.openRead(archive):
+                    Installer.notify.warning("Failed to open archive " + archive)
+                    continue
+                
+                # We don't iterate over getNumSubfiles because we're
+                # removing subfiles while we're iterating over them.
+                subfiles = mf.getSubfileNames()
+                for subfile in subfiles:
+                    # We do *NOT* call vfs.exists here in case the package is mounted.
+                    if Filename(package.getPackageDir(), subfile).exists():
+                        mf.removeSubfile(subfile)
+                
+                # This seems essential for mf.close() not to crash later.
+                mf.repack()
+                
+                # If we have no subfiles left, we can just remove the multifile.
+                if mf.getNumSubfiles() == 0:
+                    Installer.notify.info("Removing empty archive " + package.uncompressedArchive.filename)
+                    mf.close()
+                    archive.unlink()
+                else:
+                    mf.close()
+        
+        # Write out our own contents.xml file.
+        doc = TiXmlDocument()
+        decl = TiXmlDeclaration("1.0", "utf-8", "")
+        doc.InsertEndChild(decl)
+        
+        xcontents = TiXmlElement("contents")
+        for package in packages:
+            xpackage = TiXmlElement('package')
+            xpackage.SetAttribute('name', package.packageName)
+            if package.platform:
+                xpackage.SetAttribute('platform', package.platform)
+            if package.packageVersion:
+                xpackage.SetAttribute('version', version)
+                xpackage.SetAttribute('filename', package.packageName + "/" + package.packageVersion + "/" + package.descFileBasename)
+            else:
+                xpackage.SetAttribute('filename', package.packageName + "/" + package.descFileBasename)
+            xcontents.InsertEndChild(xpackage)
+
+        doc.InsertEndChild(xcontents)
+        doc.SaveFile(Filename(hostDir, "contents.xml").toOsSpecific())
 
     def buildAll(self, outputDir = "."):
         """ Creates a (graphical) installer for every known platform.
@@ -289,45 +361,26 @@ class Installer:
         print >>controlfile, "Description: %s" % self.fullname
         print >>controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6, libssl0.9.8"
         controlfile.close()
-        postinst = open(Filename(tempdir, "postinst").toOsSpecific(), "w")
-        print >>postinst, "#!/bin/sh"
-        print >>postinst, "/usr/bin/%s --prep" % self.shortname.lower()
-        print >>postinst, "chmod -R 777 /usr/share/%s" % self.shortname.lower()
-        print >>postinst, "chmod -R 555 /usr/share/%s/hosts" % self.shortname.lower()
-        postinst.close()
-        os.chmod(Filename(tempdir, "postinst").toOsSpecific(), 0755)
-        postrmfile = open(Filename(tempdir, "postrm").toOsSpecific(), "w")
-        print >>postrmfile, "#!/bin/sh"
-        print >>postrmfile, "rm -rf /usr/share/%s" % self.shortname.lower()
-        postrmfile.close()
-        os.chmod(Filename(tempdir, "postrm").toOsSpecific(), 0755)
         Filename(tempdir, "usr/bin/").makeDir()
-        self.standalone.tokens["root_dir"] = "/usr/share/" + self.shortname.lower()
+        if self.includeRequires:
+            self.standalone.tokens["host_dir"] = "/usr/lib/" + self.shortname.lower()
+        elif "host_dir" in self.standalone.tokens:
+            del self.standalone.tokens["host_dir"]
         self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform)
         if not self.licensefile.empty():
             Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
             shutil.copyfile(self.licensefile.toOsSpecific(), Filename(tempdir, "usr/share/doc/%s/copyright" % self.shortname.lower()).toOsSpecific())
-        rootDir = Filename(tempdir, "usr/share/" + self.shortname.lower())
-        rootDir.makeDir()
-        Filename(rootDir, "log").makeDir()
-        Filename(rootDir, "prc").makeDir()
-        Filename(rootDir, "start").makeDir()
-        Filename(rootDir, "certs").makeDir()
-        self.installPackagesInto(rootDir, platform)
+        hostDir = Filename(tempdir, "usr/lib/" + self.shortname.lower())
+        hostDir.makeDir()
+        self.installPackagesInto(hostDir, platform)
 
         # Create a control.tar.gz file in memory
         controlfile = Filename(tempdir, "control")
-        postinstfile = Filename(tempdir, "postinst")
-        postrmfile = Filename(tempdir, "postrm")
         controltargz = CachedFile()
         controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
         controltarfile.add(controlfile.toOsSpecific(), "control")
-        controltarfile.add(postinstfile.toOsSpecific(), "postinst")
-        controltarfile.add(postrmfile.toOsSpecific(), "postrm")
         controltarfile.close()
         controlfile.unlink()
-        postinstfile.unlink()
-        postrmfile.unlink()
 
         # Create the data.tar.gz file in the temporary directory
         datatargz = CachedFile()
@@ -352,7 +405,7 @@ class Installer:
         if (len(datatargz.str) & 1): debfile.write("\x0A")
         debfile.close()
         try:
-            base.appRunner.rmtree(tempdir)
+            appRunner.rmtree(tempdir)
         except:
             try: shutil.rmtree(tempdir.toOsSpecific())
             except: pass
@@ -367,11 +420,14 @@ class Installer:
         # Create the executable for the application bundle
         exefile = Filename(output, "Contents/MacOS/" + self.shortname)
         exefile.makeDir()
-        self.standalone.tokens["root_dir"] = "../Resources"
+        if self.includeRequires:
+            self.standalone.tokens["host_dir"] = "../Resources"
+        elif "host_dir" in self.standalone.tokens:
+            del self.standalone.tokens["host_dir"]
         self.standalone.build(exefile, platform)
-        rootDir = Filename(output, "Contents/Resources/")
-        rootDir.makeDir()
-        self.installPackagesInto(rootDir, platform)
+        hostDir = Filename(output, "Contents/Resources/")
+        hostDir.makeDir()
+        self.installPackagesInto(hostDir, platform)
         
         # Create the application plist file.
         # Although it might make more sense to use Python's plistlib module here,
@@ -510,13 +566,6 @@ class Installer:
         plist.write('</plist>\n')
         plist.close()
         
-        postflight = open(Filename(output, "Contents/Resources/postflight").toOsSpecific(), "w")
-        print >>postflight, '#!/bin/sh'
-        print >>postflight, 'chmod -R 777 "%s"' % appname
-        print >>postflight, 'chmod -R 755 "%s/hosts/"' % appname
-        postflight.close()
-        os.chmod(Filename(output, "Contents/Resources/postflight").toOsSpecific(), 0755)
-        
         if hasattr(tarfile, "PAX_FORMAT"):
             archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT)
         else:
@@ -569,13 +618,16 @@ class Installer:
 
         exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
         exefile.unlink()
-        self.standalone.tokens["root_dir"] = "."
+        if self.includeRequires:
+            self.standalone.tokens["host_dir"] = "."
+        elif "host_dir" in self.standalone.tokens:
+            del self.standalone.tokens["host_dir"]
         self.standalone.build(exefile, platform)
         
-        # Temporary directory to store the rootdir in
-        rootDir = Filename.temporary("", self.shortname.lower() + "_exe_", "") + "/"
-        rootDir.makeDir()
-        self.installPackagesInto(rootDir, platform)
+        # Temporary directory to store the hostdir in
+        hostDir = Filename.temporary("", self.shortname.lower() + "_exe_", "") + "/"
+        hostDir.makeDir()
+        self.installPackagesInto(hostDir, platform)
 
         nsifile = Filename(Filename.getTempDirectory(), self.shortname + ".nsi")
         nsifile.unlink()
@@ -625,11 +677,11 @@ class Installer:
         for f in extrafiles:
             nsi.write('  File "%s"\n' % f.toOsSpecific())
         curdir = ""
-        for root, dirs, files in os.walk(rootDir.toOsSpecific()):
+        for root, dirs, files in os.walk(hostDir.toOsSpecific()):
             for name in files:
                 file = Filename.fromOsSpecific(os.path.join(root, name))
                 file.makeAbsolute()
-                file.makeRelativeTo(rootDir)
+                file.makeRelativeTo(hostDir)
                 outdir = file.getDirname().replace('/', '\\')
                 if curdir != outdir:
                     nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
@@ -670,8 +722,8 @@ class Installer:
 
         nsifile.unlink()
         try:
-            base.appRunner.rmtree(rootDir)
+            appRunner.rmtree(hostDir)
         except:
-            try: shutil.rmtree(rootDir.toOsSpecific())
+            try: shutil.rmtree(hostDir.toOsSpecific())
             except: pass
         return output

+ 20 - 9
direct/src/p3d/HostInfo.py

@@ -118,6 +118,10 @@ class HostInfo:
             # We've already got one.
             return True
 
+        if self.appRunner.verifyContents == self.appRunner.P3DVCNever:
+            # Not allowed to.
+            return False
+
         rf = None
         if http:
             if not redownload and self.appRunner and self.appRunner.superMirrorUrl:
@@ -183,6 +187,10 @@ class HostInfo:
         not. """
         assert self.hasContentsFile
 
+        if self.appRunner.verifyContents == self.appRunner.P3DVCNever:
+            # Not allowed to.
+            return False
+
         url = self.hostUrlPrefix + 'contents.xml'
         self.notify.info("Redownloading %s" % (url))
 
@@ -215,7 +223,9 @@ class HostInfo:
     def hasCurrentContentsFile(self):
         """ Returns true if a contents.xml file has been successfully
         read for this host and is still current, false otherwise. """
-        if not self.appRunner or self.appRunner.verifyContents == self.appRunner.P3DVCNone:
+        if not self.appRunner \
+            or self.appRunner.verifyContents == self.appRunner.P3DVCNone \
+            or self.appRunner.verifyContents == self.appRunner.P3DVCNever:
             # If we're not asking to verify contents, then
             # contents.xml files never expires.
             return self.hasContentsFile
@@ -342,14 +352,15 @@ class HostInfo:
         self.hasContentsFile = True
 
         # Now save the contents.xml file into the standard location.
-        assert self.hostDir
-        filename = Filename(self.hostDir, 'contents.xml')
-        filename.makeDir()
-        if freshDownload:
-            doc.SaveFile(filename.toOsSpecific())
-        else:
-            if filename != tempFilename:
-                tempFilename.copyTo(filename)
+        if self.appRunner.verifyContents != self.appRunner.P3DVCNever:
+            assert self.hostDir
+            filename = Filename(self.hostDir, 'contents.xml')
+            filename.makeDir()
+            if freshDownload:
+                doc.SaveFile(filename.toOsSpecific())
+            else:
+                if filename != tempFilename:
+                    tempFilename.copyTo(filename)
 
         return True
 

+ 54 - 22
direct/src/p3d/PackageInfo.py

@@ -263,42 +263,44 @@ class PackageInfo:
             # We've already got one.
             yield self.stepComplete; return
 
-        self.http = http
-
-        func = lambda step, self = self: self.__downloadFile(
-            None, self.descFile,
-            urlbase = self.descFile.filename,
-            filename = self.descFileBasename)
-        step = self.InstallStep(func, self.descFile.size, self.downloadFactor, 'downloadDesc')
-
-        for token in step.func():
-            if token == self.stepContinue:
-                yield token
-            else:
-                break
+        if self.host.appRunner and self.host.appRunner.verifyContents != self.host.appRunner.P3DVCNever:
+            # We're allowed to download it.
+            self.http = http
 
-        while token == self.restartDownload:
-            # Try again.
             func = lambda step, self = self: self.__downloadFile(
                 None, self.descFile,
                 urlbase = self.descFile.filename,
                 filename = self.descFileBasename)
             step = self.InstallStep(func, self.descFile.size, self.downloadFactor, 'downloadDesc')
+
             for token in step.func():
                 if token == self.stepContinue:
                     yield token
                 else:
                     break
 
-        if token == self.stepFailed:
-            # Couldn't download the desc file.
-            yield self.stepFailed; return
+            while token == self.restartDownload:
+                # Try again.
+                func = lambda step, self = self: self.__downloadFile(
+                    None, self.descFile,
+                    urlbase = self.descFile.filename,
+                    filename = self.descFileBasename)
+                step = self.InstallStep(func, self.descFile.size, self.downloadFactor, 'downloadDesc')
+                for token in step.func():
+                    if token == self.stepContinue:
+                        yield token
+                    else:
+                        break
 
-        assert token == self.stepComplete
+            if token == self.stepFailed:
+                # Couldn't download the desc file.
+                yield self.stepFailed; return
 
-        filename = Filename(self.getPackageDir(), self.descFileBasename)
-        # Now that we've written the desc file, make it read-only.
-        os.chmod(filename.toOsSpecific(), 0444)
+            assert token == self.stepComplete
+
+            filename = Filename(self.getPackageDir(), self.descFileBasename)
+            # Now that we've written the desc file, make it read-only.
+            os.chmod(filename.toOsSpecific(), 0444)
 
         if not self.__readDescFile():
             # Weird, it passed the hash check, but we still can't read
@@ -409,6 +411,12 @@ class PackageInfo:
         pc.start()
 
         self.hasPackage = False
+        
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # We're not allowed to download anything.
+            self.installPlans = []
+            pc.stop()
+            return
 
         if self.asMirror:
             # If we're just downloading a mirror archive, we only need
@@ -520,6 +528,10 @@ class PackageInfo:
         """ Returns true if the archive and all extractable files are
         already correct on disk, false otherwise. """
 
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # Assume that everything is just fine.
+            return True
+
         # Get a list of all of the files in the directory, so we can
         # remove files that don't belong.
         contents = self.__scanDirectoryRecursively(self.getPackageDir()) 
@@ -604,6 +616,10 @@ class PackageInfo:
             # We've already got one.
             yield self.stepComplete; return
 
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # We're not allowed to download anything. Assume it's already downloaded.
+            yield self.stepComplete; return
+
         # We should have an install plan by the time we get here.
         assert self.installPlans
 
@@ -715,6 +731,10 @@ class PackageInfo:
         packageDir.  Yields one of stepComplete, stepFailed, 
         restartDownload, or stepContinue. """
 
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # We're not allowed to download anything.
+            yield self.stepFailed; return
+
         self.updated = True
 
         if not urlbase:
@@ -895,6 +915,10 @@ class PackageInfo:
         archive.  Yields one of stepComplete, stepFailed, 
         restartDownload, or stepContinue. """
 
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # We're not allowed to!
+            yield self.stepFailed; return
+
         self.updated = True
 
         sourcePathname = Filename(self.getPackageDir(), self.compressedArchive.filename)
@@ -945,6 +969,10 @@ class PackageInfo:
             self.hasPackage = True
             yield self.stepComplete; return
 
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # We're not allowed to!
+            yield self.stepFailed; return
+
         self.updated = True
 
         mfPathname = Filename(self.getPackageDir(), self.uncompressedArchive.filename)
@@ -1090,6 +1118,10 @@ class PackageInfo:
         if not hasattr(PandaModules, 'TiXmlDocument'):
             return
 
+        if self.host.appRunner and self.host.appRunner.verifyContents == self.host.appRunner.P3DVCNever:
+            # Not allowed to write any files to the package directory.
+            return
+
         if self.updated:
             # If we've just installed a new version of the package,
             # re-measure the actual disk space used.

+ 2 - 0
direct/src/p3d/pdeploy.py

@@ -229,6 +229,8 @@ if deploy_mode == 'standalone':
                 s.build(Filename(outputDir, platform + "/" + shortname), platform)
 
 elif deploy_mode == 'installer':
+    if includeRequires:
+        tokens["verify_contents"] = "never"
     i = Installer(shortname, fullname, appFilename, version, tokens = tokens)
     i.licensename = licensename
     i.licensefile = licensefile

+ 6 - 4
direct/src/plugin/load_plugin.cxx

@@ -138,7 +138,8 @@ load_plugin(const string &p3d_plugin_filename,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
-            const string &root_dir, ostream &logfile) {
+            const string &root_dir, const string &host_dir,
+            ostream &logfile) {
   if (plugin_loaded) {
     return true;
   }
@@ -254,7 +255,7 @@ load_plugin(const string &p3d_plugin_filename,
                    verify_contents, platform,
                    log_directory, log_basename,
                    trusted_environment, console_environment,
-                   root_dir, logfile)) {
+                   root_dir, host_dir, logfile)) {
     unload_dso();
     return false;
   }
@@ -277,7 +278,8 @@ init_plugin(const string &contents_filename, const string &host_url,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
-            const string &root_dir, ostream &logfile) {
+            const string &root_dir, const string &host_dir,
+            ostream &logfile) {
 
   // Ensure that all of the function pointers have been found.
   if (P3D_initialize_ptr == NULL ||
@@ -369,7 +371,7 @@ init_plugin(const string &contents_filename, const string &host_url,
                           host_url.c_str(), verify_contents, platform.c_str(),
                           log_directory.c_str(), log_basename.c_str(),
                           trusted_environment, console_environment, 
-                          root_dir.c_str())) {
+                          root_dir.c_str(), host_dir.c_str())) {
     // Oops, failure to initialize.
     logfile
       << "Failed to initialize plugin (passed API version " 

+ 2 - 2
direct/src/plugin/load_plugin.h

@@ -67,13 +67,13 @@ load_plugin(const string &p3d_plugin_filename,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
-            const string &root_dir, ostream &logfile);
+            const string &root_dir, const string &host_dir, ostream &logfile);
 bool
 init_plugin(const string &contents_filename, const string &host_url, 
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
-            const string &root_dir, ostream &logfile);
+            const string &root_dir, const string &host_dir, ostream &logfile);
 
 void unload_plugin();
 bool is_plugin_loaded();

+ 45 - 30
direct/src/plugin/p3dHost.cxx

@@ -23,12 +23,13 @@
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DHost::Constructor
 //       Access: Private
-//  Description: Use P3DInstanceManager::get_host() to construct a new
-//               P3DHost.
+//  Description: Use P3DInstanceManager::get_host() to construct a
+//               new P3DHost.
 ////////////////////////////////////////////////////////////////////
 P3DHost::
-P3DHost(const string &host_url) :
-  _host_url(host_url) 
+P3DHost(const string &host_url, const string &host_dir) :
+  _host_url(host_url), 
+  _host_dir(host_dir)
 {
   // Ensure that the download URL ends with a slash.
   _host_url_prefix = _host_url;
@@ -116,7 +117,8 @@ get_alt_host(const string &alt_host) {
 ////////////////////////////////////////////////////////////////////
 bool P3DHost::
 has_current_contents_file(P3DInstanceManager *inst_mgr) const {
-  if (inst_mgr->get_verify_contents() == P3D_VC_none) {
+  if (inst_mgr->get_verify_contents() == P3D_VC_never
+    || inst_mgr->get_verify_contents() == P3D_VC_none) {
     // If we're not asking to verify contents, then contents.xml files
     // never expire.
     return has_contents_file();
@@ -250,36 +252,41 @@ read_contents_file(const string &contents_filename, bool fresh_download) {
     }
   }
 
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  
   if (_host_dir.empty()) {
     determine_host_dir("");
   }
   assert(!_host_dir.empty());
-  mkdir_complete(_host_dir, nout);
-
+  
   string standard_filename = _host_dir + "/contents.xml";
-  if (fresh_download) {
-    if (!save_xml_file(&doc, standard_filename)) {
-      nout << "Couldn't save to " << standard_filename << "\n";
-    }
-  } else {
-    if (standardize_filename(standard_filename) != 
-        standardize_filename(contents_filename)) {
-      if (!copy_file(contents_filename, standard_filename)) {
-        nout << "Couldn't copy to " << standard_filename << "\n";
+  
+  if (inst_mgr->get_verify_contents() != P3D_VC_never) {
+    mkdir_complete(_host_dir, nout);
+
+    if (fresh_download) {
+      if (!save_xml_file(&doc, standard_filename)) {
+        nout << "Couldn't save to " << standard_filename << "\n";
+      }
+    } else {
+      if (standardize_filename(standard_filename) != 
+          standardize_filename(contents_filename)) {
+        if (!copy_file(contents_filename, standard_filename)) {
+          nout << "Couldn't copy to " << standard_filename << "\n";
+        }
       }
     }
-  }
 
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-  if (_host_url == inst_mgr->get_host_url()) {
-    // If this is also the plugin host, then copy the contents.xml
-    // file into the root Panda directory as well, for the next plugin
-    // iteration.
-    string top_filename = inst_mgr->get_root_dir() + "/contents.xml";
-    if (standardize_filename(top_filename) != 
-        standardize_filename(standard_filename)) {
-      if (!copy_file(standard_filename, top_filename)) {
-        nout << "Couldn't copy to " << top_filename << "\n";
+    if (_host_url == inst_mgr->get_host_url()) {
+      // If this is also the plugin host, then copy the contents.xml
+      // file into the root Panda directory as well, for the next plugin
+      // iteration.
+      string top_filename = inst_mgr->get_root_dir() + "/contents.xml";
+      if (standardize_filename(top_filename) != 
+          standardize_filename(standard_filename)) {
+        if (!copy_file(standard_filename, top_filename)) {
+          nout << "Couldn't copy to " << top_filename << "\n";
+        }
       }
     }
   }
@@ -305,7 +312,9 @@ read_xhost(TiXmlElement *xhost) {
   if (host_dir_basename == NULL) {
     host_dir_basename = "";
   }
-  determine_host_dir(host_dir_basename);
+  if (_host_dir.empty()) {
+    determine_host_dir(host_dir_basename);
+  }
 
   // Get the "download" URL, which is the source from which we
   // download everything other than the contents.xml file.
@@ -610,6 +619,14 @@ uninstall() {
     nout << "Cannot uninstall " << _descriptive_name << ": host directory not yet known.\n";
     return;
   }
+  
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  
+  // Check if we're even allowed to.
+  if (inst_mgr->get_verify_contents() == P3D_VC_never) {
+    nout << "Not allowed to uninstall " << _descriptive_name << ".\n";
+    return;
+  }
 
   // First, explicitly uninstall each of our packages.
   Packages::iterator mi;
@@ -624,8 +641,6 @@ uninstall() {
 
   // Then, uninstall the host itself.
   nout << "Uninstalling " << _descriptive_name << " from " << _host_dir << "\n";
-
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   inst_mgr->delete_directory_recursively(_host_dir);
   inst_mgr->forget_host(this);
 }

+ 1 - 1
direct/src/plugin/p3dHost.h

@@ -30,7 +30,7 @@ class P3DPackage;
 ////////////////////////////////////////////////////////////////////
 class P3DHost {
 private:
-  P3DHost(const string &host_url);
+  P3DHost(const string &host_url, const string &host_dir = "");
   ~P3DHost();
 
 public:

+ 18 - 11
direct/src/plugin/p3dInstance.cxx

@@ -505,10 +505,6 @@ set_p3d_filename(const string &p3d_filename, const int &p3d_offset) {
 ////////////////////////////////////////////////////////////////////
 void P3DInstance::
 set_wparams(const P3DWindowParams &wparams) {
-  if (is_failed()) {
-    return;
-  }
-
   bool prev_got_wparams = _got_wparams;
   _got_wparams = true;
   _wparams = wparams;
@@ -531,7 +527,15 @@ set_wparams(const P3DWindowParams &wparams) {
     } else {
       make_splash_window();
     }
-    
+  }
+  
+  // It doesn't make much sense to go further than this point
+  // if the instance is already in the failed state.
+  if (is_failed()) {
+    return;
+  }
+  
+  if (_wparams.get_window_type() != P3D_WT_hidden) {
 #ifdef __APPLE__
     // On Mac, we have to communicate the results of the rendering
     // back via shared memory, instead of directly parenting windows
@@ -1534,12 +1538,15 @@ uninstall_packages() {
   }
 
   // Also clean up the start directory, if we have a custom start dir.
-  string start_dir_suffix = get_start_dir_suffix();
-  if (!start_dir_suffix.empty()) {
-    P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-    string start_dir = inst_mgr->get_root_dir() + "/start" + start_dir_suffix;
-    nout << "Cleaning up start directory " << start_dir << "\n";
-    inst_mgr->delete_directory_recursively(start_dir);
+  // We won't do this if verify_contents is 'none'.
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (inst_mgr->get_verify_contents() != P3D_VC_never) {
+    string start_dir_suffix = get_start_dir_suffix();
+    if (!start_dir_suffix.empty()) {
+      string start_dir = inst_mgr->get_root_dir() + "/start" + start_dir_suffix;
+      nout << "Cleaning up start directory " << start_dir << "\n";
+      inst_mgr->delete_directory_recursively(start_dir);
+    }
   }
 
   return true;

+ 9 - 2
direct/src/plugin/p3dInstanceManager.cxx

@@ -194,7 +194,8 @@ initialize(int api_version, const string &contents_filename,
            const string &host_url, P3D_verify_contents verify_contents,
            const string &platform, const string &log_directory,
            const string &log_basename, bool trusted_environment,
-           bool console_environment, const string &root_dir) {
+           bool console_environment,
+           const string &root_dir, const string &host_dir) {
   _api_version = api_version;
   _host_url = host_url;
   _verify_contents = verify_contents;
@@ -236,6 +237,8 @@ initialize(int api_version, const string &contents_filename,
   } else {
     _root_dir = root_dir;
   }
+  
+  _host_dir = host_dir;
 
   // Allow the caller (e.g. panda3d.exe) to specify a log directory.
   // Or, allow the developer to compile one in.
@@ -641,7 +644,7 @@ get_host(const string &host_url) {
     return (*pi).second;
   }
 
-  P3DHost *host = new P3DHost(host_url);
+  P3DHost *host = new P3DHost(host_url, _host_dir);
   bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second;
   assert(inserted);
 
@@ -1384,6 +1387,9 @@ create_runtime_environment() {
        << ", host_url = " << _host_url
        << ", verify_contents = " << _verify_contents
        << "\n";
+  if (!_host_dir.empty()) {
+    nout << "_host_dir = " << _host_dir << "\n";
+  }
   nout << "api_version = " << _api_version << "\n";
 
   // Make the certificate directory.
@@ -1458,3 +1464,4 @@ nt_thread_run() {
   }
   _notify_ready.release();
 }
+

+ 3 - 1
direct/src/plugin/p3dInstanceManager.h

@@ -58,7 +58,8 @@ public:
                   const string &log_basename,
                   bool trusted_environment,
                   bool console_environment,
-                  const string &root_dir = "");
+                  const string &root_dir = "",
+                  const string &host_dir = "");
 
   inline bool is_initialized() const;
   inline void reconsider_runtime_environment();
@@ -164,6 +165,7 @@ private:
   int _api_version;
   string _host_url;
   string _root_dir;
+  string _host_dir;
   string _certs_dir;
   P3D_verify_contents _verify_contents;
   string _platform;

+ 65 - 12
direct/src/plugin/p3dPackage.cxx

@@ -229,6 +229,12 @@ remove_instance(P3DInstance *inst) {
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 mark_used() {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (inst_mgr->get_verify_contents() == P3D_VC_never) {
+    // We're not allowed to create any files in the package directory.
+    return;
+  }
+
   // Unlike the Python variant of this function, we don't mess around
   // with updating the disk space or anything.
   string filename = get_package_dir() + "/usage.xml";
@@ -404,6 +410,12 @@ download_contents_file() {
     return;
   }
 
+  // Don't download it if we're not allowed to.
+  if (inst_mgr->get_verify_contents() == P3D_VC_never) {
+    contents_file_download_finished(false);
+    return;
+  }
+
   // Download contents.xml to a temporary filename first, in case
   // multiple packages are downloading it simultaneously.
   if (_temp_contents_file != NULL) {
@@ -426,17 +438,24 @@ void P3DPackage::
 contents_file_download_finished(bool success) {
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   if (!_host->has_current_contents_file(inst_mgr)) {
-    if (!success || !_host->read_contents_file(_temp_contents_file->get_filename(), true)) {
-      nout << "Couldn't read " << *_temp_contents_file << "\n";
+    if (!success || _temp_contents_file == NULL ||
+      !_host->read_contents_file(_temp_contents_file->get_filename(), true)) {
+      
+      if (_temp_contents_file) {
+        nout << "Couldn't read " << *_temp_contents_file << "\n";
+      }
 
       // Maybe we can read an already-downloaded contents.xml file.
       string standard_filename = _host->get_host_dir() + "/contents.xml";
       if (_host->get_host_dir().empty() || 
           !_host->read_contents_file(standard_filename, false)) {
         // Couldn't even read that.  Fail.
+        nout << "Couldn't read " << standard_filename << "\n";
         report_done(false);
-        delete _temp_contents_file;
-        _temp_contents_file = NULL;
+        if (_temp_contents_file) {
+          delete _temp_contents_file;
+          _temp_contents_file = NULL;
+        }
         return;
       }
     }
@@ -444,8 +463,10 @@ contents_file_download_finished(bool success) {
     
   // The file is correctly installed by now; we can remove the
   // temporary file.
-  delete _temp_contents_file;
-  _temp_contents_file = NULL;
+  if (_temp_contents_file) {
+    delete _temp_contents_file;
+    _temp_contents_file = NULL;
+  }
 
   host_got_contents_file();
 }
@@ -480,6 +501,12 @@ redownload_contents_file(P3DPackage::Download *download) {
     return;
   }
   
+  // Don't download it if we're not allowed to.
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (inst_mgr->get_verify_contents() == P3D_VC_never) {
+    return;
+  }
+  
   set_saved_download(download);
 
   // Download contents.xml to a temporary filename first.
@@ -526,8 +553,10 @@ contents_file_redownload_finished(bool success) {
   }
     
   // We no longer need the temporary file.
-  delete _temp_contents_file;
-  _temp_contents_file = NULL;
+  if (_temp_contents_file) {
+    delete _temp_contents_file;
+    _temp_contents_file = NULL;
+  }
 
   if (contents_changed) {
     // OK, the contents.xml has changed; this means we have to restart
@@ -600,7 +629,10 @@ host_got_contents_file() {
   }
 
   // Ensure the package directory exists; create it if it does not.
-  mkdir_complete(_package_dir, nout);
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (inst_mgr->get_verify_contents() != P3D_VC_never) {
+    mkdir_complete(_package_dir, nout);
+  }
   download_desc_file();
 }
 
@@ -645,7 +677,8 @@ download_desc_file() {
   local_desc_file.set_filename(_desc_file_basename);
   _desc_file_pathname = local_desc_file.get_pathname(_package_dir);
 
-  if (!local_desc_file.full_verify(_package_dir)) {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (!local_desc_file.full_verify(_package_dir) && inst_mgr->get_verify_contents() != P3D_VC_never) {
     nout << _desc_file_pathname << " is stale.\n";
 
   } else {
@@ -663,6 +696,13 @@ download_desc_file() {
     }
   }
 
+  // Don't download it if we're not allowed to.
+  if (inst_mgr->get_verify_contents() == P3D_VC_never) {
+    nout << "Couldn't read " << _desc_file_pathname << "\n";
+    report_done(false);
+    return;
+  }
+
   // The desc file is not current.  Go download it.
   start_download(DT_desc_file, _desc_file.get_filename(), 
                  _desc_file_pathname, local_desc_file);
@@ -680,8 +720,11 @@ desc_file_download_finished(bool success) {
     return;
   }
 
-  // Now that we've downloaded the desc file, make it read-only.
-  chmod(_desc_file_pathname.c_str(), 0444);
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  if (inst_mgr->get_verify_contents() != P3D_VC_never) {
+    // Now that we've downloaded the desc file, make it read-only.
+    chmod(_desc_file_pathname.c_str(), 0444);
+  }
 
   if (_package_solo) {
     // No need to load it: the desc file *is* the package.
@@ -785,6 +828,13 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
     xrequires = xrequires->NextSiblingElement("requires");
   }
 
+  if (inst_mgr->get_verify_contents() == P3D_VC_never) {
+    // This means we'll just leave it at this
+    // and assume that we're finished.
+    report_done(true);
+    return;
+  }
+
   // Get a list of all of the files in the directory, so we can remove
   // files that don't belong.
   vector<string> contents, dirname_contents;
@@ -1208,6 +1258,9 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase,
                const string &pathname, const FileSpec &file_spec) {
   // Only one download should be active at a time
   assert(_active_download == NULL);
+  // This can't happen! If verify_contents is set to P3D_VC_never,
+  // we're not allowed to download anything, so we shouldn't get here.
+  assert(inst_mgr->get_verify_contents() != P3D_VC_never);
 
   // We can't explicitly support partial downloads here, because
   // Mozilla provides no interface to ask for one.  We have to trust

+ 8 - 3
direct/src/plugin/p3d_plugin.cxx

@@ -38,7 +38,8 @@ P3D_initialize(int api_version, const char *contents_filename,
                const char *host_url, P3D_verify_contents verify_contents,
                const char *platform, const char *log_directory,
                const char *log_basename, bool trusted_environment,
-               bool console_environment, const char *root_dir) {
+               bool console_environment,
+               const char *root_dir, const char *host_dir) {
   if (api_version < 10 || api_version > P3D_API_VERSION) {
     // Can't accept an incompatible version.
     return false;
@@ -79,17 +80,21 @@ P3D_initialize(int api_version, const char *contents_filename,
   if (log_basename == NULL) {
     log_basename = "";
   }
-  
+
   if (api_version < 12 || root_dir == NULL) {
     root_dir = "";
   }
 
+  if (api_version < 16 || host_dir == NULL) {
+    host_dir = "";
+  }
+
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   bool result = inst_mgr->initialize(api_version, contents_filename, host_url,
                                      verify_contents, platform,
                                      log_directory, log_basename,
                                      trusted_environment, console_environment,
-                                     root_dir);
+                                     root_dir, host_dir);
   RELEASE_LOCK(_api_lock);
   return result;
 }

+ 14 - 7
direct/src/plugin/p3d_plugin.h

@@ -79,7 +79,7 @@ extern "C" {
    (below). This number will be incremented whenever there are changes
    to any of the interface specifications defined in this header
    file. */
-#define P3D_API_VERSION 15
+#define P3D_API_VERSION 16
 
 /************************ GLOBAL FUNCTIONS **************************/
 
@@ -91,6 +91,7 @@ typedef enum {
   P3D_VC_none,
   P3D_VC_normal,
   P3D_VC_force,
+  P3D_VC_never,
 } P3D_verify_contents;
 
 /* This function should be called immediately after the core API is
@@ -110,8 +111,11 @@ typedef enum {
    If it is P3D_VC_normal, the server will be contacted whenever the
    contents.xml has expired.  If it is P3D_VC_force, each server will
    be contacted initially in all cases, and subseqeuntly only whenever
-   contents.xml has expired for that server.  Normally, a web plugin
-   should set this to P3D_VC_normal.
+   contents.xml has expired for that server.  The opposite of
+   P3D_VC_force is P3D_VC_never, which forces the plugin never to
+   contact the server and not to verify the contents at all.  This
+   option should only be used if the host directory is prepopulated.
+   Normally, a web plugin should set this to P3D_VC_normal.
 
    If platform is not NULL or empty, it specifies the current platform
    string; otherwise, the compiled-in default is used.  This should
@@ -137,13 +141,16 @@ typedef enum {
    p3d file will be run without checking its signature.  Normally, a
    browser plugin should set this false.
 
-   Finally, console_environment should be set true to indicate that we
-   are running within a text-based console, and expect to preserve the
-   current working directory, and also see standard output, or false
+   Furthermore, console_environment should be set true to indicate that
+   we are running within a text-based console, and expect to preserve
+   the current working directory, and also see standard output, or false
    to indicate that we are running within a GUI environment, and
    expect none of these.  Normally, a browser plugin should set this
    false.
 
+   Finally, root_dir and host_dir can be set to override the default
+   root and package directories.  Normally, you don't need to set them.
+
    This function returns true if the core API is valid and uses a
    compatible API, false otherwise.  If it returns false, the host
    should not call any more functions in this API, and should
@@ -154,7 +161,7 @@ P3D_initialize_func(int api_version, const char *contents_filename,
                     const char *platform,
                     const char *log_directory, const char *log_basename,
                     bool trusted_environment, bool console_environment,
-                    const char *root_dir);
+                    const char *root_dir, const char *host_dir);
 
 /* This function should be called to unload the core API.  It will
    release all internally-allocated memory and return the core API to

+ 1 - 1
direct/src/plugin_activex/PPInstance.cpp

@@ -525,7 +525,7 @@ int PPInstance::LoadPlugin( const std::string& dllFilename )
       string contents_filename = m_rootDir + "/contents.xml";
       if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
                        P3D_VC_normal, "", "", "", false, false, 
-                       m_rootDir, nout)) {
+                       m_rootDir, "", nout)) {
         nout << "Unable to launch core API in " << pathname << "\n";
         error = 1;
       } else {

+ 1 - 1
direct/src/plugin_npapi/ppInstance.cxx

@@ -1593,7 +1593,7 @@ do_load_plugin() {
   string contents_filename = _root_dir + "/contents.xml";
   if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
                    P3D_VC_normal, "", "", "", false, false, 
-                   _root_dir, nout)) {
+                   _root_dir, "", nout)) {
     nout << "Unable to launch core API in " << pathname << "\n";
     set_failed();
     return;

+ 25 - 2
direct/src/plugin_standalone/p3dEmbed.cxx

@@ -81,6 +81,7 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
   string keyword;
   string value;
   string root_dir;
+  string host_dir;
   while (true) {
     if (curchr == EOF) {
       cerr << "Truncated stream\n";
@@ -109,10 +110,24 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
           _win_width = atoi(value.c_str());
         } else if (keyword == "height") {
           _win_height = atoi(value.c_str());
+        } else if (keyword == "log_basename") {
+          _log_basename = value;
         } else if (keyword == "root_dir") {
           root_dir = value;
+        } else if (keyword == "host_dir") {
+          host_dir = value;
         } else if (keyword == "verify_contents") {
-          _verify_contents = (P3D_verify_contents)atoi(value.c_str());
+          if (value == "never") {
+            _verify_contents = P3D_VC_never;
+          } else if (value == "force") {
+            _verify_contents = P3D_VC_force;
+          } else if (value == "normal") {
+            _verify_contents = P3D_VC_normal;
+          } else if (value == "none") {
+            _verify_contents = P3D_VC_none;
+          } else {
+            _verify_contents = (P3D_verify_contents)atoi(value.c_str());
+          }
         }
       }
       curstr = "";
@@ -139,6 +154,13 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
     root_dir_f.make_absolute(f.get_dirname());
     _root_dir = root_dir_f.to_os_specific();
   }
+  
+  // Make the host directory absolute
+  if (!host_dir.empty()) {
+    Filename host_dir_f(host_dir);
+    host_dir_f.make_absolute(f.get_dirname());
+    _host_dir = host_dir_f.to_os_specific();
+  }
 
   // Initialize the core API by directly assigning all of the function
   // pointers.
@@ -185,6 +207,7 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
   // Calling the executable with --prep just prepares the directory
   // structure, this is usually invoked in the installer.
   if (argc == 2 && strcmp(argv[1], "--prep") == 0) {
+    cerr << "Invoking the prepare step is deprecated, please rebuild the application using a more recent version of pdeploy\n";
     _window_type = P3D_WT_hidden;
     _log_basename = "prep";
     P3D_token token;
@@ -200,7 +223,7 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
   // function pointers.  This will also call P3D_initialize().
   if (!init_plugin("", _host_url, _verify_contents, _this_platform, 
                    _log_dirname, _log_basename, true, _console_environment,
-                   _root_dir, cerr)) {
+                   _root_dir, _host_dir, cerr)) {
     cerr << "Unable to launch core API\n";
     return 1;
   }

+ 1 - 1
direct/src/plugin_standalone/panda3d.cxx

@@ -802,7 +802,7 @@ get_core_api() {
   if (!load_plugin(pathname, contents_filename.to_os_specific(),
                    _host_url, _verify_contents, _this_platform, _log_dirname,
                    _log_basename, trusted_environment, _console_environment,
-                   _root_dir, cerr)) {
+                   _root_dir, "", cerr)) {
     cerr << "Unable to launch core API in " << pathname << "\n";
     return false;
   }

+ 1 - 0
direct/src/plugin_standalone/panda3dBase.h

@@ -73,6 +73,7 @@ protected:
 protected:
   string _host_url;
   string _root_dir;
+  string _host_dir;
   string _log_dirname;
   string _log_basename;
   string _this_platform;