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
     P3DVCNone = 0
     P3DVCNormal = 1
     P3DVCNormal = 1
     P3DVCForce = 2
     P3DVCForce = 2
+    P3DVCNever = 3
 
 
     # Also from p3d_plugin.h
     # Also from p3d_plugin.h
     P3D_CONTENTS_DEFAULT_MAX_AGE = 5
     P3D_CONTENTS_DEFAULT_MAX_AGE = 5
@@ -570,6 +571,11 @@ class AppRunner(DirectObject):
                 totalSize += packageData.totalSize
                 totalSize += packageData.totalSize
         self.notify.info("Total Panda3D disk space used: %s MB" % (
         self.notify.info("Total Panda3D disk space used: %s MB" % (
             (totalSize + 524288) / 1048576))
             (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.notify.info("Configured max usage is: %s MB" % (
             (self.maxDiskUsage + 524288) / 1048576))
             (self.maxDiskUsage + 524288) / 1048576))
         if totalSize <= self.maxDiskUsage:
         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
 import os, sys, subprocess, tarfile, shutil, time, zipfile, glob
 from direct.directnotify.DirectNotifyGlobal import *
 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
 from direct.p3d.HostInfo import HostInfo
 
 
 class CachedFile:
 class CachedFile:
@@ -27,7 +29,7 @@ class Standalone:
         
         
         hostDir = Filename(Filename.getTempDirectory(), 'pdeploy/')
         hostDir = Filename(Filename.getTempDirectory(), 'pdeploy/')
         hostDir.makeDir()
         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()
         self.http = HTTPClient.getGlobalPtr()
         if not self.host.hasContentsFile:
         if not self.host.hasContentsFile:
@@ -61,6 +63,9 @@ class Standalone:
         
         
         if platform == None:
         if platform == None:
             platform = PandaSystem.getPlatform()
             platform = PandaSystem.getPlatform()
+        
+        vfs = VirtualFileSystem.getGlobalPtr()
+        
         for package in self.host.getPackages(name = "p3dembed", platform = platform):
         for package in self.host.getPackages(name = "p3dembed", platform = platform):
             if not package.downloadDescFile(self.http):
             if not package.downloadDescFile(self.http):
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
@@ -75,7 +80,7 @@ class Standalone:
             else:
             else:
                 p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed" % package.platform)
                 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))
                 Standalone.notify.warning("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
                 continue
             
             
@@ -104,7 +109,11 @@ class Standalone:
         output.makeDir()
         output.makeDir()
         ohandle = open(output.toOsSpecific(), "wb")
         ohandle = open(output.toOsSpecific(), "wb")
         ohandle.write(p3dembed_data)
         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%s=%s" % token)
         ohandle.write("\0\0")
         ohandle.write("\0\0")
         
         
@@ -134,11 +143,12 @@ class Standalone:
             return []
             return []
         
         
         filenames = []
         filenames = []
+        vfs = VirtualFileSystem.getGlobalPtr()
         for e in package.extracts:
         for e in package.extracts:
             if e.basename not in ["p3dembed", "p3dembed.exe"]:
             if e.basename not in ["p3dembed", "p3dembed.exe"]:
                 filename = Filename(package.getPackageDir(), e.filename)
                 filename = Filename(package.getPackageDir(), e.filename)
                 filename.makeAbsolute()
                 filename.makeAbsolute()
-                if filename.exists():
+                if vfs.exists(filename):
                     filenames.append(filename)
                     filenames.append(filename)
                 else:
                 else:
                     Standalone.notify.error("%s mentioned in xml, but does not exist" % e.filename)
                     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')))
                     self.requirements.append((p3dRequires.Attribute('name'), p3dRequires.Attribute('version')))
                     p3dRequires = p3dRequires.NextSiblingElement('requires')
                     p3dRequires = p3dRequires.NextSiblingElement('requires')
 
 
-    def installPackagesInto(self, rootDir, platform):
+    def installPackagesInto(self, hostDir, platform):
         """ Installs the packages required by the .p3d file into
         """ 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:
         if not self.includeRequires:
             return
             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.hasContentsFile:
             if not host.readContentsFile():
             if not host.readContentsFile():
                 if not host.downloadContentsFile(self.http):
                 if not host.downloadContentsFile(self.http):
@@ -203,15 +215,17 @@ class Installer:
         
         
         for name, version in self.requirements:
         for name, version in self.requirements:
             package = host.getPackage(name, version, platform)
             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):
             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
                 continue
             if not package.downloadPackage(self.http):
             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
                 continue
         
         
         # Also install the 'images' package from the same host that p3dembed was downloaded from.
         # 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.hasContentsFile:
             if not host.readContentsFile():
             if not host.readContentsFile():
                 if not host.downloadContentsFile(self.http):
                 if not host.downloadContentsFile(self.http):
@@ -219,13 +233,71 @@ class Installer:
                     return
                     return
         
         
         for package in host.getPackages(name = "images"):
         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):
             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
                 continue
             if not package.downloadPackage(self.http):
             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
                 continue
             break
             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 = "."):
     def buildAll(self, outputDir = "."):
         """ Creates a (graphical) installer for every known platform.
         """ Creates a (graphical) installer for every known platform.
@@ -289,45 +361,26 @@ class Installer:
         print >>controlfile, "Description: %s" % self.fullname
         print >>controlfile, "Description: %s" % self.fullname
         print >>controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6, libssl0.9.8"
         print >>controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6, libssl0.9.8"
         controlfile.close()
         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()
         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)
         self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform)
         if not self.licensefile.empty():
         if not self.licensefile.empty():
             Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
             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())
             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
         # Create a control.tar.gz file in memory
         controlfile = Filename(tempdir, "control")
         controlfile = Filename(tempdir, "control")
-        postinstfile = Filename(tempdir, "postinst")
-        postrmfile = Filename(tempdir, "postrm")
         controltargz = CachedFile()
         controltargz = CachedFile()
         controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
         controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
         controltarfile.add(controlfile.toOsSpecific(), "control")
         controltarfile.add(controlfile.toOsSpecific(), "control")
-        controltarfile.add(postinstfile.toOsSpecific(), "postinst")
-        controltarfile.add(postrmfile.toOsSpecific(), "postrm")
         controltarfile.close()
         controltarfile.close()
         controlfile.unlink()
         controlfile.unlink()
-        postinstfile.unlink()
-        postrmfile.unlink()
 
 
         # Create the data.tar.gz file in the temporary directory
         # Create the data.tar.gz file in the temporary directory
         datatargz = CachedFile()
         datatargz = CachedFile()
@@ -352,7 +405,7 @@ class Installer:
         if (len(datatargz.str) & 1): debfile.write("\x0A")
         if (len(datatargz.str) & 1): debfile.write("\x0A")
         debfile.close()
         debfile.close()
         try:
         try:
-            base.appRunner.rmtree(tempdir)
+            appRunner.rmtree(tempdir)
         except:
         except:
             try: shutil.rmtree(tempdir.toOsSpecific())
             try: shutil.rmtree(tempdir.toOsSpecific())
             except: pass
             except: pass
@@ -367,11 +420,14 @@ class Installer:
         # Create the executable for the application bundle
         # Create the executable for the application bundle
         exefile = Filename(output, "Contents/MacOS/" + self.shortname)
         exefile = Filename(output, "Contents/MacOS/" + self.shortname)
         exefile.makeDir()
         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)
         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.
         # Create the application plist file.
         # Although it might make more sense to use Python's plistlib module here,
         # Although it might make more sense to use Python's plistlib module here,
@@ -510,13 +566,6 @@ class Installer:
         plist.write('</plist>\n')
         plist.write('</plist>\n')
         plist.close()
         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"):
         if hasattr(tarfile, "PAX_FORMAT"):
             archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT)
             archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT)
         else:
         else:
@@ -569,13 +618,16 @@ class Installer:
 
 
         exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
         exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
         exefile.unlink()
         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)
         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 = Filename(Filename.getTempDirectory(), self.shortname + ".nsi")
         nsifile.unlink()
         nsifile.unlink()
@@ -625,11 +677,11 @@ class Installer:
         for f in extrafiles:
         for f in extrafiles:
             nsi.write('  File "%s"\n' % f.toOsSpecific())
             nsi.write('  File "%s"\n' % f.toOsSpecific())
         curdir = ""
         curdir = ""
-        for root, dirs, files in os.walk(rootDir.toOsSpecific()):
+        for root, dirs, files in os.walk(hostDir.toOsSpecific()):
             for name in files:
             for name in files:
                 file = Filename.fromOsSpecific(os.path.join(root, name))
                 file = Filename.fromOsSpecific(os.path.join(root, name))
                 file.makeAbsolute()
                 file.makeAbsolute()
-                file.makeRelativeTo(rootDir)
+                file.makeRelativeTo(hostDir)
                 outdir = file.getDirname().replace('/', '\\')
                 outdir = file.getDirname().replace('/', '\\')
                 if curdir != outdir:
                 if curdir != outdir:
                     nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
                     nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
@@ -670,8 +722,8 @@ class Installer:
 
 
         nsifile.unlink()
         nsifile.unlink()
         try:
         try:
-            base.appRunner.rmtree(rootDir)
+            appRunner.rmtree(hostDir)
         except:
         except:
-            try: shutil.rmtree(rootDir.toOsSpecific())
+            try: shutil.rmtree(hostDir.toOsSpecific())
             except: pass
             except: pass
         return output
         return output

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

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

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

@@ -263,42 +263,44 @@ class PackageInfo:
             # We've already got one.
             # We've already got one.
             yield self.stepComplete; return
             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(
             func = lambda step, self = self: self.__downloadFile(
                 None, self.descFile,
                 None, self.descFile,
                 urlbase = self.descFile.filename,
                 urlbase = self.descFile.filename,
                 filename = self.descFileBasename)
                 filename = self.descFileBasename)
             step = self.InstallStep(func, self.descFile.size, self.downloadFactor, 'downloadDesc')
             step = self.InstallStep(func, self.descFile.size, self.downloadFactor, 'downloadDesc')
+
             for token in step.func():
             for token in step.func():
                 if token == self.stepContinue:
                 if token == self.stepContinue:
                     yield token
                     yield token
                 else:
                 else:
                     break
                     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():
         if not self.__readDescFile():
             # Weird, it passed the hash check, but we still can't read
             # Weird, it passed the hash check, but we still can't read
@@ -409,6 +411,12 @@ class PackageInfo:
         pc.start()
         pc.start()
 
 
         self.hasPackage = False
         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 self.asMirror:
             # If we're just downloading a mirror archive, we only need
             # 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
         """ Returns true if the archive and all extractable files are
         already correct on disk, false otherwise. """
         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
         # Get a list of all of the files in the directory, so we can
         # remove files that don't belong.
         # remove files that don't belong.
         contents = self.__scanDirectoryRecursively(self.getPackageDir()) 
         contents = self.__scanDirectoryRecursively(self.getPackageDir()) 
@@ -604,6 +616,10 @@ class PackageInfo:
             # We've already got one.
             # We've already got one.
             yield self.stepComplete; return
             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.
         # We should have an install plan by the time we get here.
         assert self.installPlans
         assert self.installPlans
 
 
@@ -715,6 +731,10 @@ class PackageInfo:
         packageDir.  Yields one of stepComplete, stepFailed, 
         packageDir.  Yields one of stepComplete, stepFailed, 
         restartDownload, or stepContinue. """
         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
         self.updated = True
 
 
         if not urlbase:
         if not urlbase:
@@ -895,6 +915,10 @@ class PackageInfo:
         archive.  Yields one of stepComplete, stepFailed, 
         archive.  Yields one of stepComplete, stepFailed, 
         restartDownload, or stepContinue. """
         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
         self.updated = True
 
 
         sourcePathname = Filename(self.getPackageDir(), self.compressedArchive.filename)
         sourcePathname = Filename(self.getPackageDir(), self.compressedArchive.filename)
@@ -945,6 +969,10 @@ class PackageInfo:
             self.hasPackage = True
             self.hasPackage = True
             yield self.stepComplete; return
             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
         self.updated = True
 
 
         mfPathname = Filename(self.getPackageDir(), self.uncompressedArchive.filename)
         mfPathname = Filename(self.getPackageDir(), self.uncompressedArchive.filename)
@@ -1090,6 +1118,10 @@ class PackageInfo:
         if not hasattr(PandaModules, 'TiXmlDocument'):
         if not hasattr(PandaModules, 'TiXmlDocument'):
             return
             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 self.updated:
             # If we've just installed a new version of the package,
             # If we've just installed a new version of the package,
             # re-measure the actual disk space used.
             # 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)
                 s.build(Filename(outputDir, platform + "/" + shortname), platform)
 
 
 elif deploy_mode == 'installer':
 elif deploy_mode == 'installer':
+    if includeRequires:
+        tokens["verify_contents"] = "never"
     i = Installer(shortname, fullname, appFilename, version, tokens = tokens)
     i = Installer(shortname, fullname, appFilename, version, tokens = tokens)
     i.licensename = licensename
     i.licensename = licensename
     i.licensefile = licensefile
     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,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             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) {
   if (plugin_loaded) {
     return true;
     return true;
   }
   }
@@ -254,7 +255,7 @@ load_plugin(const string &p3d_plugin_filename,
                    verify_contents, platform,
                    verify_contents, platform,
                    log_directory, log_basename,
                    log_directory, log_basename,
                    trusted_environment, console_environment,
                    trusted_environment, console_environment,
-                   root_dir, logfile)) {
+                   root_dir, host_dir, logfile)) {
     unload_dso();
     unload_dso();
     return false;
     return false;
   }
   }
@@ -277,7 +278,8 @@ init_plugin(const string &contents_filename, const string &host_url,
             P3D_verify_contents verify_contents, const string &platform,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             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.
   // Ensure that all of the function pointers have been found.
   if (P3D_initialize_ptr == NULL ||
   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(),
                           host_url.c_str(), verify_contents, platform.c_str(),
                           log_directory.c_str(), log_basename.c_str(),
                           log_directory.c_str(), log_basename.c_str(),
                           trusted_environment, console_environment, 
                           trusted_environment, console_environment, 
-                          root_dir.c_str())) {
+                          root_dir.c_str(), host_dir.c_str())) {
     // Oops, failure to initialize.
     // Oops, failure to initialize.
     logfile
     logfile
       << "Failed to initialize plugin (passed API version " 
       << "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,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             bool trusted_environment, bool console_environment,
-            const string &root_dir, ostream &logfile);
+            const string &root_dir, const string &host_dir, ostream &logfile);
 bool
 bool
 init_plugin(const string &contents_filename, const string &host_url, 
 init_plugin(const string &contents_filename, const string &host_url, 
             P3D_verify_contents verify_contents, const string &platform,
             P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             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();
 void unload_plugin();
 bool is_plugin_loaded();
 bool is_plugin_loaded();

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

@@ -23,12 +23,13 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DHost::Constructor
 //     Function: P3DHost::Constructor
 //       Access: Private
 //       Access: Private
-//  Description: Use P3DInstanceManager::get_host() to construct a new
-//               P3DHost.
+//  Description: Use P3DInstanceManager::get_host() to construct a
+//               new P3DHost.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 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.
   // Ensure that the download URL ends with a slash.
   _host_url_prefix = _host_url;
   _host_url_prefix = _host_url;
@@ -116,7 +117,8 @@ get_alt_host(const string &alt_host) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool P3DHost::
 bool P3DHost::
 has_current_contents_file(P3DInstanceManager *inst_mgr) const {
 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
     // If we're not asking to verify contents, then contents.xml files
     // never expire.
     // never expire.
     return has_contents_file();
     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()) {
   if (_host_dir.empty()) {
     determine_host_dir("");
     determine_host_dir("");
   }
   }
   assert(!_host_dir.empty());
   assert(!_host_dir.empty());
-  mkdir_complete(_host_dir, nout);
-
+  
   string standard_filename = _host_dir + "/contents.xml";
   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) {
   if (host_dir_basename == NULL) {
     host_dir_basename = "";
     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
   // Get the "download" URL, which is the source from which we
   // download everything other than the contents.xml file.
   // download everything other than the contents.xml file.
@@ -610,6 +619,14 @@ uninstall() {
     nout << "Cannot uninstall " << _descriptive_name << ": host directory not yet known.\n";
     nout << "Cannot uninstall " << _descriptive_name << ": host directory not yet known.\n";
     return;
     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.
   // First, explicitly uninstall each of our packages.
   Packages::iterator mi;
   Packages::iterator mi;
@@ -624,8 +641,6 @@ uninstall() {
 
 
   // Then, uninstall the host itself.
   // Then, uninstall the host itself.
   nout << "Uninstalling " << _descriptive_name << " from " << _host_dir << "\n";
   nout << "Uninstalling " << _descriptive_name << " from " << _host_dir << "\n";
-
-  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   inst_mgr->delete_directory_recursively(_host_dir);
   inst_mgr->delete_directory_recursively(_host_dir);
   inst_mgr->forget_host(this);
   inst_mgr->forget_host(this);
 }
 }

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

@@ -30,7 +30,7 @@ class P3DPackage;
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class P3DHost {
 class P3DHost {
 private:
 private:
-  P3DHost(const string &host_url);
+  P3DHost(const string &host_url, const string &host_dir = "");
   ~P3DHost();
   ~P3DHost();
 
 
 public:
 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::
 void P3DInstance::
 set_wparams(const P3DWindowParams &wparams) {
 set_wparams(const P3DWindowParams &wparams) {
-  if (is_failed()) {
-    return;
-  }
-
   bool prev_got_wparams = _got_wparams;
   bool prev_got_wparams = _got_wparams;
   _got_wparams = true;
   _got_wparams = true;
   _wparams = wparams;
   _wparams = wparams;
@@ -531,7 +527,15 @@ set_wparams(const P3DWindowParams &wparams) {
     } else {
     } else {
       make_splash_window();
       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__
 #ifdef __APPLE__
     // On Mac, we have to communicate the results of the rendering
     // On Mac, we have to communicate the results of the rendering
     // back via shared memory, instead of directly parenting windows
     // 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.
   // 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;
   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 &host_url, P3D_verify_contents verify_contents,
            const string &platform, const string &log_directory,
            const string &platform, const string &log_directory,
            const string &log_basename, bool trusted_environment,
            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;
   _api_version = api_version;
   _host_url = host_url;
   _host_url = host_url;
   _verify_contents = verify_contents;
   _verify_contents = verify_contents;
@@ -236,6 +237,8 @@ initialize(int api_version, const string &contents_filename,
   } else {
   } else {
     _root_dir = root_dir;
     _root_dir = root_dir;
   }
   }
+  
+  _host_dir = host_dir;
 
 
   // Allow the caller (e.g. panda3d.exe) to specify a log directory.
   // Allow the caller (e.g. panda3d.exe) to specify a log directory.
   // Or, allow the developer to compile one in.
   // Or, allow the developer to compile one in.
@@ -641,7 +644,7 @@ get_host(const string &host_url) {
     return (*pi).second;
     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;
   bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second;
   assert(inserted);
   assert(inserted);
 
 
@@ -1384,6 +1387,9 @@ create_runtime_environment() {
        << ", host_url = " << _host_url
        << ", host_url = " << _host_url
        << ", verify_contents = " << _verify_contents
        << ", verify_contents = " << _verify_contents
        << "\n";
        << "\n";
+  if (!_host_dir.empty()) {
+    nout << "_host_dir = " << _host_dir << "\n";
+  }
   nout << "api_version = " << _api_version << "\n";
   nout << "api_version = " << _api_version << "\n";
 
 
   // Make the certificate directory.
   // Make the certificate directory.
@@ -1458,3 +1464,4 @@ nt_thread_run() {
   }
   }
   _notify_ready.release();
   _notify_ready.release();
 }
 }
+

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

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

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

@@ -229,6 +229,12 @@ remove_instance(P3DInstance *inst) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
 void P3DPackage::
 mark_used() {
 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
   // Unlike the Python variant of this function, we don't mess around
   // with updating the disk space or anything.
   // with updating the disk space or anything.
   string filename = get_package_dir() + "/usage.xml";
   string filename = get_package_dir() + "/usage.xml";
@@ -404,6 +410,12 @@ download_contents_file() {
     return;
     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
   // Download contents.xml to a temporary filename first, in case
   // multiple packages are downloading it simultaneously.
   // multiple packages are downloading it simultaneously.
   if (_temp_contents_file != NULL) {
   if (_temp_contents_file != NULL) {
@@ -426,17 +438,24 @@ void P3DPackage::
 contents_file_download_finished(bool success) {
 contents_file_download_finished(bool success) {
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   if (!_host->has_current_contents_file(inst_mgr)) {
   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.
       // Maybe we can read an already-downloaded contents.xml file.
       string standard_filename = _host->get_host_dir() + "/contents.xml";
       string standard_filename = _host->get_host_dir() + "/contents.xml";
       if (_host->get_host_dir().empty() || 
       if (_host->get_host_dir().empty() || 
           !_host->read_contents_file(standard_filename, false)) {
           !_host->read_contents_file(standard_filename, false)) {
         // Couldn't even read that.  Fail.
         // Couldn't even read that.  Fail.
+        nout << "Couldn't read " << standard_filename << "\n";
         report_done(false);
         report_done(false);
-        delete _temp_contents_file;
-        _temp_contents_file = NULL;
+        if (_temp_contents_file) {
+          delete _temp_contents_file;
+          _temp_contents_file = NULL;
+        }
         return;
         return;
       }
       }
     }
     }
@@ -444,8 +463,10 @@ contents_file_download_finished(bool success) {
     
     
   // The file is correctly installed by now; we can remove the
   // The file is correctly installed by now; we can remove the
   // temporary file.
   // 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();
   host_got_contents_file();
 }
 }
@@ -480,6 +501,12 @@ redownload_contents_file(P3DPackage::Download *download) {
     return;
     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);
   set_saved_download(download);
 
 
   // Download contents.xml to a temporary filename first.
   // 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.
   // 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) {
   if (contents_changed) {
     // OK, the contents.xml has changed; this means we have to restart
     // 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.
   // 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();
   download_desc_file();
 }
 }
 
 
@@ -645,7 +677,8 @@ download_desc_file() {
   local_desc_file.set_filename(_desc_file_basename);
   local_desc_file.set_filename(_desc_file_basename);
   _desc_file_pathname = local_desc_file.get_pathname(_package_dir);
   _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";
     nout << _desc_file_pathname << " is stale.\n";
 
 
   } else {
   } 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.
   // The desc file is not current.  Go download it.
   start_download(DT_desc_file, _desc_file.get_filename(), 
   start_download(DT_desc_file, _desc_file.get_filename(), 
                  _desc_file_pathname, local_desc_file);
                  _desc_file_pathname, local_desc_file);
@@ -680,8 +720,11 @@ desc_file_download_finished(bool success) {
     return;
     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) {
   if (_package_solo) {
     // No need to load it: the desc file *is* the package.
     // 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");
     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
   // Get a list of all of the files in the directory, so we can remove
   // files that don't belong.
   // files that don't belong.
   vector<string> contents, dirname_contents;
   vector<string> contents, dirname_contents;
@@ -1208,6 +1258,9 @@ start_download(P3DPackage::DownloadType dtype, const string &urlbase,
                const string &pathname, const FileSpec &file_spec) {
                const string &pathname, const FileSpec &file_spec) {
   // Only one download should be active at a time
   // Only one download should be active at a time
   assert(_active_download == NULL);
   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
   // We can't explicitly support partial downloads here, because
   // Mozilla provides no interface to ask for one.  We have to trust
   // 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 *host_url, P3D_verify_contents verify_contents,
                const char *platform, const char *log_directory,
                const char *platform, const char *log_directory,
                const char *log_basename, bool trusted_environment,
                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) {
   if (api_version < 10 || api_version > P3D_API_VERSION) {
     // Can't accept an incompatible version.
     // Can't accept an incompatible version.
     return false;
     return false;
@@ -79,17 +80,21 @@ P3D_initialize(int api_version, const char *contents_filename,
   if (log_basename == NULL) {
   if (log_basename == NULL) {
     log_basename = "";
     log_basename = "";
   }
   }
-  
+
   if (api_version < 12 || root_dir == NULL) {
   if (api_version < 12 || root_dir == NULL) {
     root_dir = "";
     root_dir = "";
   }
   }
 
 
+  if (api_version < 16 || host_dir == NULL) {
+    host_dir = "";
+  }
+
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   bool result = inst_mgr->initialize(api_version, contents_filename, host_url,
   bool result = inst_mgr->initialize(api_version, contents_filename, host_url,
                                      verify_contents, platform,
                                      verify_contents, platform,
                                      log_directory, log_basename,
                                      log_directory, log_basename,
                                      trusted_environment, console_environment,
                                      trusted_environment, console_environment,
-                                     root_dir);
+                                     root_dir, host_dir);
   RELEASE_LOCK(_api_lock);
   RELEASE_LOCK(_api_lock);
   return result;
   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
    (below). This number will be incremented whenever there are changes
    to any of the interface specifications defined in this header
    to any of the interface specifications defined in this header
    file. */
    file. */
-#define P3D_API_VERSION 15
+#define P3D_API_VERSION 16
 
 
 /************************ GLOBAL FUNCTIONS **************************/
 /************************ GLOBAL FUNCTIONS **************************/
 
 
@@ -91,6 +91,7 @@ typedef enum {
   P3D_VC_none,
   P3D_VC_none,
   P3D_VC_normal,
   P3D_VC_normal,
   P3D_VC_force,
   P3D_VC_force,
+  P3D_VC_never,
 } P3D_verify_contents;
 } P3D_verify_contents;
 
 
 /* This function should be called immediately after the core API is
 /* 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
    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
    contents.xml has expired.  If it is P3D_VC_force, each server will
    be contacted initially in all cases, and subseqeuntly only whenever
    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
    If platform is not NULL or empty, it specifies the current platform
    string; otherwise, the compiled-in default is used.  This should
    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
    p3d file will be run without checking its signature.  Normally, a
    browser plugin should set this false.
    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
    to indicate that we are running within a GUI environment, and
    expect none of these.  Normally, a browser plugin should set this
    expect none of these.  Normally, a browser plugin should set this
    false.
    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
    This function returns true if the core API is valid and uses a
    compatible API, false otherwise.  If it returns false, the host
    compatible API, false otherwise.  If it returns false, the host
    should not call any more functions in this API, and should
    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 *platform,
                     const char *log_directory, const char *log_basename,
                     const char *log_directory, const char *log_basename,
                     bool trusted_environment, bool console_environment,
                     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
 /* This function should be called to unload the core API.  It will
    release all internally-allocated memory and return the core API to
    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";
       string contents_filename = m_rootDir + "/contents.xml";
       if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
       if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
                        P3D_VC_normal, "", "", "", false, false, 
                        P3D_VC_normal, "", "", "", false, false, 
-                       m_rootDir, nout)) {
+                       m_rootDir, "", nout)) {
         nout << "Unable to launch core API in " << pathname << "\n";
         nout << "Unable to launch core API in " << pathname << "\n";
         error = 1;
         error = 1;
       } else {
       } else {

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

@@ -1593,7 +1593,7 @@ do_load_plugin() {
   string contents_filename = _root_dir + "/contents.xml";
   string contents_filename = _root_dir + "/contents.xml";
   if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
   if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
                    P3D_VC_normal, "", "", "", false, false, 
                    P3D_VC_normal, "", "", "", false, false, 
-                   _root_dir, nout)) {
+                   _root_dir, "", nout)) {
     nout << "Unable to launch core API in " << pathname << "\n";
     nout << "Unable to launch core API in " << pathname << "\n";
     set_failed();
     set_failed();
     return;
     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 keyword;
   string value;
   string value;
   string root_dir;
   string root_dir;
+  string host_dir;
   while (true) {
   while (true) {
     if (curchr == EOF) {
     if (curchr == EOF) {
       cerr << "Truncated stream\n";
       cerr << "Truncated stream\n";
@@ -109,10 +110,24 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
           _win_width = atoi(value.c_str());
           _win_width = atoi(value.c_str());
         } else if (keyword == "height") {
         } else if (keyword == "height") {
           _win_height = atoi(value.c_str());
           _win_height = atoi(value.c_str());
+        } else if (keyword == "log_basename") {
+          _log_basename = value;
         } else if (keyword == "root_dir") {
         } else if (keyword == "root_dir") {
           root_dir = value;
           root_dir = value;
+        } else if (keyword == "host_dir") {
+          host_dir = value;
         } else if (keyword == "verify_contents") {
         } 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 = "";
       curstr = "";
@@ -139,6 +154,13 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
     root_dir_f.make_absolute(f.get_dirname());
     root_dir_f.make_absolute(f.get_dirname());
     _root_dir = root_dir_f.to_os_specific();
     _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
   // Initialize the core API by directly assigning all of the function
   // pointers.
   // pointers.
@@ -185,6 +207,7 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
   // Calling the executable with --prep just prepares the directory
   // Calling the executable with --prep just prepares the directory
   // structure, this is usually invoked in the installer.
   // structure, this is usually invoked in the installer.
   if (argc == 2 && strcmp(argv[1], "--prep") == 0) {
   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;
     _window_type = P3D_WT_hidden;
     _log_basename = "prep";
     _log_basename = "prep";
     P3D_token token;
     P3D_token token;
@@ -200,7 +223,7 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
   // function pointers.  This will also call P3D_initialize().
   // function pointers.  This will also call P3D_initialize().
   if (!init_plugin("", _host_url, _verify_contents, _this_platform, 
   if (!init_plugin("", _host_url, _verify_contents, _this_platform, 
                    _log_dirname, _log_basename, true, _console_environment,
                    _log_dirname, _log_basename, true, _console_environment,
-                   _root_dir, cerr)) {
+                   _root_dir, _host_dir, cerr)) {
     cerr << "Unable to launch core API\n";
     cerr << "Unable to launch core API\n";
     return 1;
     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(),
   if (!load_plugin(pathname, contents_filename.to_os_specific(),
                    _host_url, _verify_contents, _this_platform, _log_dirname,
                    _host_url, _verify_contents, _this_platform, _log_dirname,
                    _log_basename, trusted_environment, _console_environment,
                    _log_basename, trusted_environment, _console_environment,
-                   _root_dir, cerr)) {
+                   _root_dir, "", cerr)) {
     cerr << "Unable to launch core API in " << pathname << "\n";
     cerr << "Unable to launch core API in " << pathname << "\n";
     return false;
     return false;
   }
   }

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

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