mirror of
https://github.com/panda3d/panda3d.git
synced 2026-02-17 12:49:36 -06:00
Remove p3d/AppRunner/plugin system
Co-authored-by: rdb <git@rdb.name> Closes #734
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
// Based on Apple sample code at
|
||||
// http://developer.apple.com/internet/webcontent/examples/detectplugins_source.html
|
||||
|
||||
|
||||
// initialize global variables
|
||||
var detectableWithVB = false;
|
||||
var pluginFound = false;
|
||||
|
||||
function goURL(daURL) {
|
||||
// Assume we have Javascript 1.1 functionality.
|
||||
window.location.replace(daURL);
|
||||
return;
|
||||
}
|
||||
|
||||
function redirectCheck(pluginFound, redirectURL, redirectIfFound) {
|
||||
// check for redirection
|
||||
if( redirectURL && ((pluginFound && redirectIfFound) ||
|
||||
(!pluginFound && !redirectIfFound)) ) {
|
||||
// go away
|
||||
goURL(redirectURL);
|
||||
return pluginFound;
|
||||
} else {
|
||||
// stay here and return result of plugin detection
|
||||
return pluginFound;
|
||||
}
|
||||
}
|
||||
|
||||
function canDetectPlugins() {
|
||||
if( detectableWithVB || (navigator.plugins && navigator.plugins.length > 0) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function detectPanda3D(redirectURL, redirectIfFound) {
|
||||
pluginFound = detectPlugin('Panda3D');
|
||||
// if not found, try to detect with VisualBasic
|
||||
if(!pluginFound && detectableWithVB) {
|
||||
pluginFound = detectActiveXControl('P3DACTIVEX.P3DActiveXCtrl.1');
|
||||
}
|
||||
// check for redirection
|
||||
return redirectCheck(pluginFound, redirectURL, redirectIfFound);
|
||||
}
|
||||
|
||||
function detectPlugin() {
|
||||
// allow for multiple checks in a single pass
|
||||
var daPlugins = detectPlugin.arguments;
|
||||
// consider pluginFound to be false until proven true
|
||||
var pluginFound = false;
|
||||
// if plugins array is there and not fake
|
||||
if (navigator.plugins && navigator.plugins.length > 0) {
|
||||
var pluginsArrayLength = navigator.plugins.length;
|
||||
// for each plugin...
|
||||
for (pluginsArrayCounter=0; pluginsArrayCounter < pluginsArrayLength; pluginsArrayCounter++ ) {
|
||||
// loop through all desired names and check each against the current plugin name
|
||||
var numFound = 0;
|
||||
for(namesCounter=0; namesCounter < daPlugins.length; namesCounter++) {
|
||||
// if desired plugin name is found in either plugin name or description
|
||||
if( (navigator.plugins[pluginsArrayCounter].name.indexOf(daPlugins[namesCounter]) >= 0) ||
|
||||
(navigator.plugins[pluginsArrayCounter].description.indexOf(daPlugins[namesCounter]) >= 0) ) {
|
||||
// this name was found
|
||||
numFound++;
|
||||
}
|
||||
}
|
||||
// now that we have checked all the required names against this one plugin,
|
||||
// if the number we found matches the total number provided then we were successful
|
||||
if(numFound == daPlugins.length) {
|
||||
pluginFound = true;
|
||||
// if we've found the plugin, we can stop looking through at the rest of the plugins
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pluginFound;
|
||||
} // detectPlugin
|
||||
|
||||
|
||||
// Here we write out the VBScript block for MSIE Windows
|
||||
if ((navigator.userAgent.indexOf('MSIE') != -1) && (navigator.userAgent.indexOf('Win') != -1)) {
|
||||
document.writeln('<script language="VBscript">');
|
||||
|
||||
document.writeln('\'do a one-time test for a version of VBScript that can handle this code');
|
||||
document.writeln('detectableWithVB = False');
|
||||
document.writeln('If ScriptEngineMajorVersion >= 2 then');
|
||||
document.writeln(' detectableWithVB = True');
|
||||
document.writeln('End If');
|
||||
|
||||
document.writeln('\'this next function will detect most plugins');
|
||||
document.writeln('Function detectActiveXControl(activeXControlName)');
|
||||
document.writeln(' on error resume next');
|
||||
document.writeln(' detectActiveXControl = False');
|
||||
document.writeln(' If detectableWithVB Then');
|
||||
document.writeln(' detectActiveXControl = IsObject(CreateObject(activeXControlName))');
|
||||
document.writeln(' End If');
|
||||
document.writeln('End Function');
|
||||
|
||||
document.writeln('</script>');
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// This script injects the appropriate syntax into the document to
|
||||
// embed Panda3D, either for the ActiveX or Mozilla-based plugin.
|
||||
|
||||
// It is also possible to write browser-independent code by nesting
|
||||
// <object> tags, but this causes problems when you need to reference
|
||||
// the particular object that is actually running (which object is
|
||||
// it?) for scripting purposes.
|
||||
|
||||
// This script writes only a single <object> tag, and it can be
|
||||
// assigned the id you specify, avoiding this ambiguity.
|
||||
|
||||
var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
|
||||
var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
|
||||
var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
|
||||
|
||||
|
||||
function P3D_Generateobj(objAttrs, params, embedAttrs, imageAttrs)
|
||||
{
|
||||
var str = '';
|
||||
|
||||
if (isIE && isWin && !isOpera)
|
||||
{
|
||||
str += '<object ';
|
||||
for (var i in objAttrs)
|
||||
{
|
||||
str += i + '="' + objAttrs[i] + '" ';
|
||||
}
|
||||
str += '>';
|
||||
for (var i in params)
|
||||
{
|
||||
str += '<param name="' + i + '" value="' + params[i] + '" /> ';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
str += '<object ';
|
||||
for (var i in embedAttrs)
|
||||
{
|
||||
str += i + '="' + embedAttrs[i] + '" ';
|
||||
}
|
||||
str += '> ';
|
||||
}
|
||||
if (imageAttrs["src"]) {
|
||||
if (imageAttrs["href"]) {
|
||||
str += '<a href="' + imageAttrs["href"] + '">';
|
||||
}
|
||||
str += '<img ';
|
||||
for (var i in imageAttrs) {
|
||||
if (i != "href") {
|
||||
str += i + '="' + imageAttrs[i] + '" ';
|
||||
}
|
||||
}
|
||||
str += '>';
|
||||
if (imageAttrs["href"]) {
|
||||
str += '</a>';
|
||||
}
|
||||
}
|
||||
str += '</object>';
|
||||
|
||||
document.write(str);
|
||||
}
|
||||
|
||||
function P3D_RunContent() {
|
||||
var ret =
|
||||
P3D_GetArgs
|
||||
(arguments, "clsid:924b4927-d3ba-41ea-9f7e-8a89194ab3ac",
|
||||
"application/x-panda3d");
|
||||
P3D_Generateobj(ret.objAttrs, ret.params, ret.embedAttrs, ret.imageAttrs);
|
||||
}
|
||||
|
||||
function P3D_GetArgs(args, classid, mimeType){
|
||||
var ret = new Object();
|
||||
ret.embedAttrs = new Object();
|
||||
ret.params = new Object();
|
||||
ret.objAttrs = new Object();
|
||||
ret.imageAttrs = new Object();
|
||||
|
||||
for (var i = 0; i < args.length; i = i + 2){
|
||||
var currArg = args[i].toLowerCase();
|
||||
|
||||
switch (currArg){
|
||||
case "src":
|
||||
case "data":
|
||||
ret.embedAttrs['data'] = args[i+1];
|
||||
ret.params['data'] = args[i+1];
|
||||
break;
|
||||
|
||||
case "codebase":
|
||||
ret.objAttrs['codebase'] = args[i+1];
|
||||
break;
|
||||
|
||||
case "noplugin_img":
|
||||
ret.imageAttrs["src"] = args[i+1];
|
||||
ret.imageAttrs["border"] = '0';
|
||||
break;
|
||||
|
||||
case "noplugin_href":
|
||||
ret.imageAttrs["href"] = args[i+1];
|
||||
break;
|
||||
|
||||
case "splash_img":
|
||||
ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
|
||||
if (!ret.imageAttrs["src"]) {
|
||||
ret.imageAttrs["src"] = args[i+1];
|
||||
}
|
||||
break;
|
||||
|
||||
case "width":
|
||||
case "height":
|
||||
ret.imageAttrs[args[i]] = ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
|
||||
break;
|
||||
|
||||
case "id":
|
||||
case "align":
|
||||
case "vspace":
|
||||
case "hspace":
|
||||
case "class":
|
||||
case "title":
|
||||
case "accesskey":
|
||||
case "name":
|
||||
case "tabindex":
|
||||
ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
|
||||
break;
|
||||
|
||||
default:
|
||||
ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
|
||||
}
|
||||
}
|
||||
ret.objAttrs["classid"] = classid;
|
||||
if (mimeType) ret.embedAttrs["type"] = mimeType;
|
||||
return ret;
|
||||
}
|
||||
2
direct/src/dist/commands.py
vendored
2
direct/src/dist/commands.py
vendored
@@ -21,7 +21,7 @@ import distutils.log
|
||||
|
||||
from . import FreezeTool
|
||||
from . import pefile
|
||||
from direct.p3d.DeploymentTools import Icon
|
||||
from .icon import Icon
|
||||
import panda3d.core as p3d
|
||||
|
||||
|
||||
|
||||
269
direct/src/dist/icon.py
vendored
Normal file
269
direct/src/dist/icon.py
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
from direct.directnotify.DirectNotifyGlobal import *
|
||||
from panda3d.core import PNMImage, Filename
|
||||
|
||||
class Icon:
|
||||
""" This class is used to create an icon for various platforms. """
|
||||
notify = directNotify.newCategory("Icon")
|
||||
|
||||
def __init__(self):
|
||||
self.images = {}
|
||||
|
||||
def addImage(self, image):
|
||||
""" Adds an image to the icon. Returns False on failure, True on success.
|
||||
Only one image per size can be loaded, and the image size must be square. """
|
||||
|
||||
if not isinstance(image, PNMImage):
|
||||
fn = image
|
||||
if not isinstance(fn, Filename):
|
||||
fn = Filename.fromOsSpecific(fn)
|
||||
|
||||
image = PNMImage()
|
||||
if not image.read(fn):
|
||||
Icon.notify.warning("Image '%s' could not be read" % fn.getBasename())
|
||||
return False
|
||||
|
||||
if image.getXSize() != image.getYSize():
|
||||
Icon.notify.warning("Ignoring image without square size")
|
||||
return False
|
||||
|
||||
self.images[image.getXSize()] = image
|
||||
|
||||
return True
|
||||
|
||||
def generateMissingImages(self):
|
||||
""" Generates image sizes that should be present but aren't by scaling
|
||||
from the next higher size. """
|
||||
|
||||
for required_size in (256, 128, 48, 32, 16):
|
||||
if required_size in self.images:
|
||||
continue
|
||||
|
||||
sizes = sorted(self.images.keys())
|
||||
if required_size * 2 in sizes:
|
||||
from_size = required_size * 2
|
||||
else:
|
||||
from_size = 0
|
||||
for from_size in sizes:
|
||||
if from_size > required_size:
|
||||
break
|
||||
|
||||
if from_size > required_size:
|
||||
Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size))
|
||||
|
||||
image = PNMImage(required_size, required_size)
|
||||
if self.images[from_size].hasAlpha():
|
||||
image.addAlpha()
|
||||
image.quickFilterFrom(self.images[from_size])
|
||||
self.images[required_size] = image
|
||||
else:
|
||||
Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size))
|
||||
|
||||
def _write_bitmap(self, fp, image, size, bpp):
|
||||
""" Writes the bitmap header and data of an .ico file. """
|
||||
|
||||
fp.write(struct.pack('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
# XOR mask
|
||||
if bpp == 24:
|
||||
# Align rows to 4-byte boundary
|
||||
rowalign = b'\0' * (-(size * 3) & 3)
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
r, g, b = image.getXel(x, size - y - 1)
|
||||
fp.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255)))
|
||||
fp.write(rowalign)
|
||||
|
||||
elif bpp == 32:
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
r, g, b, a = image.getXelA(x, size - y - 1)
|
||||
fp.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255)))
|
||||
|
||||
elif bpp == 8:
|
||||
# We'll have to generate a palette of 256 colors.
|
||||
hist = PNMImage.Histogram()
|
||||
image2 = PNMImage(image)
|
||||
if image2.hasAlpha():
|
||||
image2.premultiplyAlpha()
|
||||
image2.removeAlpha()
|
||||
image2.quantize(256)
|
||||
image2.make_histogram(hist)
|
||||
colors = list(hist.get_pixels())
|
||||
assert len(colors) <= 256
|
||||
|
||||
# Write the palette.
|
||||
i = 0
|
||||
while i < 256 and i < len(colors):
|
||||
r, g, b, a = colors[i]
|
||||
fp.write(struct.pack('<BBBB', b, g, r, 0))
|
||||
i += 1
|
||||
if i < 256:
|
||||
# Fill the rest with zeroes.
|
||||
fp.write(b'\x00' * (4 * (256 - i)))
|
||||
|
||||
# Write indices. Align rows to 4-byte boundary.
|
||||
rowalign = b'\0' * (-size & 3)
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
pixel = image2.get_pixel(x, size - y - 1)
|
||||
index = colors.index(pixel)
|
||||
if index >= 256:
|
||||
# Find closest pixel instead.
|
||||
index = closest_indices[index - 256]
|
||||
fp.write(struct.pack('<B', index))
|
||||
fp.write(rowalign)
|
||||
else:
|
||||
raise ValueError("Invalid bpp %d" % (bpp))
|
||||
|
||||
# Create an AND mask, aligned to 4-byte boundary
|
||||
if image.hasAlpha() and bpp <= 8:
|
||||
rowalign = b'\0' * (-((size + 7) >> 3) & 3)
|
||||
for y in xrange(size):
|
||||
mask = 0
|
||||
num_bits = 7
|
||||
for x in xrange(size):
|
||||
a = image.get_alpha_val(x, size - y - 1)
|
||||
if a <= 1:
|
||||
mask |= (1 << num_bits)
|
||||
num_bits -= 1
|
||||
if num_bits < 0:
|
||||
fp.write(struct.pack('<B', mask))
|
||||
mask = 0
|
||||
num_bits = 7
|
||||
if num_bits < 7:
|
||||
fp.write(struct.pack('<B', mask))
|
||||
fp.write(rowalign)
|
||||
else:
|
||||
andsize = (size + 7) >> 3
|
||||
if andsize % 4 != 0:
|
||||
andsize += 4 - (andsize % 4)
|
||||
fp.write(b'\x00' * (andsize * size))
|
||||
|
||||
def makeICO(self, fn):
|
||||
""" Writes the images to a Windows ICO file. Returns True on success. """
|
||||
|
||||
if not isinstance(fn, Filename):
|
||||
fn = Filename.fromOsSpecific(fn)
|
||||
fn.setBinary()
|
||||
|
||||
# ICO files only support resolutions up to 256x256.
|
||||
count = 0
|
||||
for size in self.images.keys():
|
||||
if size < 256:
|
||||
count += 1
|
||||
if size <= 256:
|
||||
count += 1
|
||||
dataoffs = 6 + count * 16
|
||||
|
||||
ico = open(fn, 'wb')
|
||||
ico.write(struct.pack('<HHH', 0, 1, count))
|
||||
|
||||
# Write 8-bpp image headers for sizes under 256x256.
|
||||
for size, image in self.images.items():
|
||||
if size >= 256:
|
||||
continue
|
||||
ico.write(struct.pack('<BB', size, size))
|
||||
|
||||
# Calculate row sizes
|
||||
xorsize = size
|
||||
if xorsize % 4 != 0:
|
||||
xorsize += 4 - (xorsize % 4)
|
||||
andsize = (size + 7) >> 3
|
||||
if andsize % 4 != 0:
|
||||
andsize += 4 - (andsize % 4)
|
||||
datasize = 40 + 256 * 4 + (xorsize + andsize) * size
|
||||
|
||||
ico.write(struct.pack('<BBHHII', 0, 0, 1, 8, datasize, dataoffs))
|
||||
dataoffs += datasize
|
||||
|
||||
# Write 24/32-bpp image headers.
|
||||
for size, image in self.images.items():
|
||||
if size > 256:
|
||||
continue
|
||||
elif size == 256:
|
||||
ico.write(b'\0\0')
|
||||
else:
|
||||
ico.write(struct.pack('<BB', size, size))
|
||||
|
||||
# Calculate the size so we can write the offset within the file.
|
||||
if image.hasAlpha():
|
||||
bpp = 32
|
||||
xorsize = size * 4
|
||||
else:
|
||||
bpp = 24
|
||||
xorsize = size * 3 + (-(size * 3) & 3)
|
||||
andsize = (size + 7) >> 3
|
||||
if andsize % 4 != 0:
|
||||
andsize += 4 - (andsize % 4)
|
||||
datasize = 40 + (xorsize + andsize) * size
|
||||
|
||||
ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, datasize, dataoffs))
|
||||
dataoffs += datasize
|
||||
|
||||
# Now write the actual icon bitmap data.
|
||||
for size, image in self.images.items():
|
||||
if size < 256:
|
||||
self._write_bitmap(ico, image, size, 8)
|
||||
|
||||
for size, image in self.images.items():
|
||||
if size <= 256:
|
||||
bpp = 32 if image.hasAlpha() else 24
|
||||
self._write_bitmap(ico, image, size, bpp)
|
||||
|
||||
assert ico.tell() == dataoffs
|
||||
ico.close()
|
||||
|
||||
return True
|
||||
|
||||
def makeICNS(self, fn):
|
||||
""" Writes the images to an Apple ICNS file. Returns True on success. """
|
||||
|
||||
if not isinstance(fn, Filename):
|
||||
fn = Filename.fromOsSpecific(fn)
|
||||
fn.setBinary()
|
||||
|
||||
icns = open(fn, 'wb')
|
||||
icns.write(b'icns\0\0\0\0')
|
||||
|
||||
icon_types = {16: b'is32', 32: b'il32', 48: b'ih32', 128: b'it32'}
|
||||
mask_types = {16: b's8mk', 32: b'l8mk', 48: b'h8mk', 128: b't8mk'}
|
||||
png_types = {256: b'ic08', 512: b'ic09', 1024: b'ic10'}
|
||||
|
||||
pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png")
|
||||
|
||||
for size, image in sorted(self.images.items(), key=lambda item:item[0]):
|
||||
if size in png_types and pngtype is not None:
|
||||
stream = StringStream()
|
||||
image.write(stream, "", pngtype)
|
||||
pngdata = stream.data
|
||||
|
||||
icns.write(png_types[size])
|
||||
icns.write(struct.pack('>I', len(pngdata)))
|
||||
icns.write(pngdata)
|
||||
|
||||
elif size in icon_types:
|
||||
# If it has an alpha channel, we write out a mask too.
|
||||
if image.hasAlpha():
|
||||
icns.write(mask_types[size])
|
||||
icns.write(struct.pack('>I', size * size + 8))
|
||||
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
icns.write(struct.pack('<B', int(image.getAlpha(x, y) * 255)))
|
||||
|
||||
icns.write(icon_types[size])
|
||||
icns.write(struct.pack('>I', size * size * 4 + 8))
|
||||
|
||||
for y in xrange(size):
|
||||
for x in xrange(size):
|
||||
r, g, b = image.getXel(x, y)
|
||||
icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255)))
|
||||
|
||||
length = icns.tell()
|
||||
icns.seek(4)
|
||||
icns.write(struct.pack('>I', length))
|
||||
icns.close()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,96 +0,0 @@
|
||||
__all__ = ["DWBPackageInstaller"]
|
||||
|
||||
from direct.p3d.PackageInstaller import PackageInstaller
|
||||
from direct.gui.DirectWaitBar import DirectWaitBar
|
||||
from direct.gui import DirectGuiGlobals as DGG
|
||||
|
||||
class DWBPackageInstaller(DirectWaitBar, PackageInstaller):
|
||||
""" This class presents a PackageInstaller that also inherits from
|
||||
DirectWaitBar, so it updates its own GUI as it downloads.
|
||||
|
||||
Specify perPackage = True to make the progress bar reset for each
|
||||
package, or False (the default) to show one continuous progress
|
||||
bar for all packages.
|
||||
|
||||
Specify updateText = True (the default) to update the text label
|
||||
with the name of the package or False to leave it up to you to set
|
||||
it.
|
||||
|
||||
You can specify a callback function with finished = func; this
|
||||
function will be called, with one boolean parameter, when the
|
||||
download has completed. The parameter will be true on success, or
|
||||
false on failure.
|
||||
"""
|
||||
|
||||
def __init__(self, appRunner, parent = None, **kw):
|
||||
PackageInstaller.__init__(self, appRunner)
|
||||
|
||||
optiondefs = (
|
||||
('borderWidth', (0.01, 0.01), None),
|
||||
('relief', DGG.SUNKEN, self.setRelief),
|
||||
('range', 1, self.setRange),
|
||||
('barBorderWidth', (0.01, 0.01), self.setBarBorderWidth),
|
||||
('barColor', (0.424, 0.647, 0.878, 1), self.setBarColor),
|
||||
('barRelief', DGG.RAISED, self.setBarRelief),
|
||||
('text', 'Starting', self.setText),
|
||||
('text_pos', (0, -0.025), None),
|
||||
('text_scale', 0.1, None),
|
||||
('perPackage', False, None),
|
||||
('updateText', True, None),
|
||||
('finished', None, None),
|
||||
)
|
||||
self.defineoptions(kw, optiondefs)
|
||||
DirectWaitBar.__init__(self, parent, **kw)
|
||||
self.initialiseoptions(DWBPackageInstaller)
|
||||
self.updateBarStyle()
|
||||
|
||||
# Hidden by default until the download begins.
|
||||
self.hide()
|
||||
|
||||
def cleanup(self):
|
||||
PackageInstaller.cleanup(self)
|
||||
DirectWaitBar.destroy(self)
|
||||
|
||||
def destroy(self):
|
||||
PackageInstaller.cleanup(self)
|
||||
DirectWaitBar.destroy(self)
|
||||
|
||||
def packageStarted(self, package):
|
||||
""" This callback is made for each package between
|
||||
downloadStarted() and downloadFinished() to indicate the start
|
||||
of a new package. """
|
||||
if self['updateText']:
|
||||
self['text'] = 'Installing %s' % (package.getFormattedName())
|
||||
self.show()
|
||||
|
||||
def packageProgress(self, package, progress):
|
||||
""" This callback is made repeatedly between packageStarted()
|
||||
and packageFinished() to update the current progress on the
|
||||
indicated package only. The progress value ranges from 0
|
||||
(beginning) to 1 (complete). """
|
||||
|
||||
if self['perPackage']:
|
||||
self['value'] = progress * self['range']
|
||||
|
||||
def downloadProgress(self, overallProgress):
|
||||
""" This callback is made repeatedly between downloadStarted()
|
||||
and downloadFinished() to update the current progress through
|
||||
all packages. The progress value ranges from 0 (beginning) to
|
||||
1 (complete). """
|
||||
|
||||
if not self['perPackage']:
|
||||
self['value'] = overallProgress * self['range']
|
||||
|
||||
def downloadFinished(self, success):
|
||||
""" This callback is made when all of the packages have been
|
||||
downloaded and installed (or there has been some failure). If
|
||||
all packages where successfully installed, success is True.
|
||||
|
||||
If there were no packages that required downloading, this
|
||||
callback will be made immediately, *without* a corresponding
|
||||
call to downloadStarted(). """
|
||||
|
||||
self.hide()
|
||||
|
||||
if self['finished']:
|
||||
self['finished'](success)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,246 +0,0 @@
|
||||
__all__ = ["FileSpec"]
|
||||
|
||||
import os
|
||||
import time
|
||||
from panda3d.core import Filename, HashVal, VirtualFileSystem
|
||||
|
||||
class FileSpec:
|
||||
""" This class represents a disk file whose hash and size
|
||||
etc. were read from an xml file. This class provides methods to
|
||||
verify whether the file on disk matches the version demanded by
|
||||
the xml. """
|
||||
|
||||
def __init__(self):
|
||||
self.actualFile = None
|
||||
self.filename = None
|
||||
self.size = 0
|
||||
self.timestamp = 0
|
||||
self.hash = None
|
||||
|
||||
def fromFile(self, packageDir, filename, pathname = None, st = None):
|
||||
""" Reads the file information from the indicated file. If st
|
||||
is supplied, it is the result of os.stat on the filename. """
|
||||
|
||||
vfs = VirtualFileSystem.getGlobalPtr()
|
||||
|
||||
filename = Filename(filename)
|
||||
if pathname is None:
|
||||
pathname = Filename(packageDir, filename)
|
||||
|
||||
self.filename = str(filename)
|
||||
self.basename = filename.getBasename()
|
||||
|
||||
if st is None:
|
||||
st = os.stat(pathname.toOsSpecific())
|
||||
self.size = st.st_size
|
||||
self.timestamp = int(st.st_mtime)
|
||||
|
||||
self.readHash(pathname)
|
||||
|
||||
def readHash(self, pathname):
|
||||
""" Reads the hash only from the indicated pathname. """
|
||||
|
||||
hv = HashVal()
|
||||
hv.hashFile(pathname)
|
||||
self.hash = hv.asHex()
|
||||
|
||||
|
||||
def loadXml(self, xelement):
|
||||
""" Reads the file information from the indicated XML
|
||||
element. """
|
||||
|
||||
self.filename = xelement.Attribute('filename')
|
||||
self.basename = None
|
||||
if self.filename:
|
||||
self.basename = Filename(self.filename).getBasename()
|
||||
|
||||
size = xelement.Attribute('size')
|
||||
try:
|
||||
self.size = int(size)
|
||||
except:
|
||||
self.size = 0
|
||||
|
||||
timestamp = xelement.Attribute('timestamp')
|
||||
try:
|
||||
self.timestamp = int(timestamp)
|
||||
except:
|
||||
self.timestamp = 0
|
||||
|
||||
self.hash = xelement.Attribute('hash')
|
||||
|
||||
def storeXml(self, xelement):
|
||||
""" Adds the file information to the indicated XML
|
||||
element. """
|
||||
|
||||
if self.filename:
|
||||
xelement.SetAttribute('filename', self.filename)
|
||||
if self.size:
|
||||
xelement.SetAttribute('size', str(self.size))
|
||||
if self.timestamp:
|
||||
xelement.SetAttribute('timestamp', str(int(self.timestamp)))
|
||||
if self.hash:
|
||||
xelement.SetAttribute('hash', self.hash)
|
||||
|
||||
def storeMiniXml(self, xelement):
|
||||
""" Adds the just the "mini" file information--size and
|
||||
hash--to the indicated XML element. """
|
||||
|
||||
if self.size:
|
||||
xelement.SetAttribute('size', str(self.size))
|
||||
if self.hash:
|
||||
xelement.SetAttribute('hash', self.hash)
|
||||
|
||||
def quickVerify(self, packageDir = None, pathname = None,
|
||||
notify = None, correctSelf = False):
|
||||
""" Performs a quick test to ensure the file has not been
|
||||
modified. This test is vulnerable to people maliciously
|
||||
attempting to fool the program (by setting datestamps etc.).
|
||||
|
||||
if correctSelf is True, then any discrepency is corrected by
|
||||
updating the appropriate fields internally, making the
|
||||
assumption that the file on disk is the authoritative version.
|
||||
|
||||
Returns true if it is intact, false if it is incorrect. If
|
||||
correctSelf is true, raises OSError if the self-update is
|
||||
impossible (for instance, because the file does not exist)."""
|
||||
|
||||
if not pathname:
|
||||
pathname = Filename(packageDir, self.filename)
|
||||
try:
|
||||
st = os.stat(pathname.toOsSpecific())
|
||||
except OSError:
|
||||
# If the file is missing, the file fails.
|
||||
if notify:
|
||||
notify.debug("file not found: %s" % (pathname))
|
||||
if correctSelf:
|
||||
raise
|
||||
return False
|
||||
|
||||
if st.st_size != self.size:
|
||||
# If the size is wrong, the file fails.
|
||||
if notify:
|
||||
notify.debug("size wrong: %s" % (pathname))
|
||||
if correctSelf:
|
||||
self.__correctHash(packageDir, pathname, st, notify)
|
||||
return False
|
||||
|
||||
if int(st.st_mtime) == self.timestamp:
|
||||
# If the size is right and the timestamp is right, the
|
||||
# file passes.
|
||||
if notify:
|
||||
notify.debug("file ok: %s" % (pathname))
|
||||
return True
|
||||
|
||||
if notify:
|
||||
notify.debug("modification time wrong: %s" % (pathname))
|
||||
|
||||
# If the size is right but the timestamp is wrong, the file
|
||||
# soft-fails. We follow this up with a hash check.
|
||||
if not self.checkHash(packageDir, pathname, st):
|
||||
# Hard fail, the hash is wrong.
|
||||
if notify:
|
||||
notify.debug("hash check wrong: %s" % (pathname))
|
||||
notify.debug(" found %s, expected %s" % (self.actualFile.hash, self.hash))
|
||||
if correctSelf:
|
||||
self.__correctHash(packageDir, pathname, st, notify)
|
||||
return False
|
||||
|
||||
if notify:
|
||||
notify.debug("hash check ok: %s" % (pathname))
|
||||
|
||||
# The hash is OK after all. Change the file's timestamp back
|
||||
# to what we expect it to be, so we can quick-verify it
|
||||
# successfully next time.
|
||||
if correctSelf:
|
||||
# Or update our own timestamp.
|
||||
self.__correctTimestamp(pathname, st, notify)
|
||||
return False
|
||||
else:
|
||||
self.__updateTimestamp(pathname, st)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def fullVerify(self, packageDir = None, pathname = None, notify = None):
|
||||
""" Performs a more thorough test to ensure the file has not
|
||||
been modified. This test is less vulnerable to malicious
|
||||
attacks, since it reads and verifies the entire file.
|
||||
|
||||
Returns true if it is intact, false if it needs to be
|
||||
redownloaded. """
|
||||
|
||||
if not pathname:
|
||||
pathname = Filename(packageDir, self.filename)
|
||||
try:
|
||||
st = os.stat(pathname.toOsSpecific())
|
||||
except OSError:
|
||||
# If the file is missing, the file fails.
|
||||
if notify:
|
||||
notify.debug("file not found: %s" % (pathname))
|
||||
return False
|
||||
|
||||
if st.st_size != self.size:
|
||||
# If the size is wrong, the file fails;
|
||||
if notify:
|
||||
notify.debug("size wrong: %s" % (pathname))
|
||||
return False
|
||||
|
||||
if not self.checkHash(packageDir, pathname, st):
|
||||
# Hard fail, the hash is wrong.
|
||||
if notify:
|
||||
notify.debug("hash check wrong: %s" % (pathname))
|
||||
notify.debug(" found %s, expected %s" % (self.actualFile.hash, self.hash))
|
||||
return False
|
||||
|
||||
if notify:
|
||||
notify.debug("hash check ok: %s" % (pathname))
|
||||
|
||||
# The hash is OK. If the timestamp is wrong, change it back
|
||||
# to what we expect it to be, so we can quick-verify it
|
||||
# successfully next time.
|
||||
if int(st.st_mtime) != self.timestamp:
|
||||
self.__updateTimestamp(pathname, st)
|
||||
|
||||
return True
|
||||
|
||||
def __updateTimestamp(self, pathname, st):
|
||||
# On Windows, we have to change the file to read-write before
|
||||
# we can successfully update its timestamp.
|
||||
try:
|
||||
os.chmod(pathname.toOsSpecific(), 0o755)
|
||||
os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
|
||||
os.chmod(pathname.toOsSpecific(), 0o555)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def __correctTimestamp(self, pathname, st, notify):
|
||||
""" Corrects the internal timestamp to match the one on
|
||||
disk. """
|
||||
if notify:
|
||||
notify.info("Correcting timestamp of %s to %d (%s)" % (
|
||||
self.filename, st.st_mtime, time.asctime(time.localtime(st.st_mtime))))
|
||||
self.timestamp = int(st.st_mtime)
|
||||
|
||||
def checkHash(self, packageDir, pathname, st):
|
||||
""" Returns true if the file has the expected md5 hash, false
|
||||
otherwise. As a side effect, stores a FileSpec corresponding
|
||||
to the on-disk file in self.actualFile. """
|
||||
|
||||
fileSpec = FileSpec()
|
||||
fileSpec.fromFile(packageDir, self.filename,
|
||||
pathname = pathname, st = st)
|
||||
self.actualFile = fileSpec
|
||||
|
||||
return (fileSpec.hash == self.hash)
|
||||
|
||||
def __correctHash(self, packageDir, pathname, st, notify):
|
||||
""" Corrects the internal hash to match the one on disk. """
|
||||
if not self.actualFile:
|
||||
self.checkHash(packageDir, pathname, st)
|
||||
|
||||
if notify:
|
||||
notify.info("Correcting hash %s to %s" % (
|
||||
self.filename, self.actualFile.hash))
|
||||
self.hash = self.actualFile.hash
|
||||
self.size = self.actualFile.size
|
||||
self.timestamp = self.actualFile.timestamp
|
||||
@@ -1,751 +0,0 @@
|
||||
__all__ = ["HostInfo"]
|
||||
|
||||
from panda3d.core import HashVal, Filename, PandaSystem, DocumentSpec, Ramfile
|
||||
from panda3d.core import HTTPChannel, ConfigVariableInt
|
||||
from panda3d import core
|
||||
from direct.p3d.PackageInfo import PackageInfo
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
import time
|
||||
|
||||
class HostInfo:
|
||||
""" This class represents a particular download host serving up
|
||||
Panda3D packages. It is the Python equivalent of the P3DHost
|
||||
class in the core API. """
|
||||
|
||||
notify = directNotify.newCategory("HostInfo")
|
||||
|
||||
def __init__(self, hostUrl, appRunner = None, hostDir = None,
|
||||
rootDir = None, asMirror = False, perPlatform = None):
|
||||
|
||||
""" You must specify either an appRunner or a hostDir to the
|
||||
HostInfo constructor.
|
||||
|
||||
If you pass asMirror = True, it means that this HostInfo
|
||||
object is to be used to populate a "mirror" folder, a
|
||||
duplicate (or subset) of the contents hosted by a server.
|
||||
This means when you use this HostInfo to download packages, it
|
||||
will only download the compressed archive file and leave it
|
||||
there. At the moment, mirror folders do not download old
|
||||
patch files from the server.
|
||||
|
||||
If you pass perPlatform = True, then files are unpacked into a
|
||||
platform-specific directory, which is appropriate when you
|
||||
might be downloading multiple platforms. The default is
|
||||
perPlatform = False, which means all files are unpacked into
|
||||
the host directory directly, without an intervening
|
||||
platform-specific directory name. If asMirror is True, then
|
||||
the default is perPlatform = True.
|
||||
|
||||
Note that perPlatform is also restricted by the individual
|
||||
package's specification. """
|
||||
|
||||
self.__setHostUrl(hostUrl)
|
||||
self.appRunner = appRunner
|
||||
self.rootDir = rootDir
|
||||
if rootDir is None and appRunner:
|
||||
self.rootDir = appRunner.rootDir
|
||||
|
||||
if hostDir and not isinstance(hostDir, Filename):
|
||||
hostDir = Filename.fromOsSpecific(hostDir)
|
||||
|
||||
self.hostDir = hostDir
|
||||
self.asMirror = asMirror
|
||||
self.perPlatform = perPlatform
|
||||
if perPlatform is None:
|
||||
self.perPlatform = asMirror
|
||||
|
||||
# Initially false, this is set true when the contents file is
|
||||
# successfully read.
|
||||
self.hasContentsFile = False
|
||||
|
||||
# This is the time value at which the current contents file is
|
||||
# no longer valid.
|
||||
self.contentsExpiration = 0
|
||||
|
||||
# Contains the md5 hash of the original contents.xml file.
|
||||
self.contentsSpec = FileSpec()
|
||||
|
||||
# descriptiveName will be filled in later, when the
|
||||
# contents file is read.
|
||||
self.descriptiveName = None
|
||||
|
||||
# A list of known mirrors for this host, all URL's guaranteed
|
||||
# to end with a slash.
|
||||
self.mirrors = []
|
||||
|
||||
# A map of keyword -> altHost URL's. An altHost is different
|
||||
# than a mirror; an altHost is an alternate URL to download a
|
||||
# different (e.g. testing) version of this host's contents.
|
||||
# It is rarely used.
|
||||
self.altHosts = {}
|
||||
|
||||
# This is a dictionary of packages by (name, version). It
|
||||
# will be filled in when the contents file is read.
|
||||
self.packages = {}
|
||||
|
||||
if self.appRunner and self.appRunner.verifyContents != self.appRunner.P3DVCForce:
|
||||
# Attempt to pre-read the existing contents.xml; maybe it
|
||||
# will be current enough for our purposes.
|
||||
self.readContentsFile()
|
||||
|
||||
def __setHostUrl(self, hostUrl):
|
||||
""" Assigns self.hostUrl, and related values. """
|
||||
self.hostUrl = hostUrl
|
||||
|
||||
if not self.hostUrl:
|
||||
# A special case: the URL will be set later.
|
||||
self.hostUrlPrefix = None
|
||||
self.downloadUrlPrefix = None
|
||||
else:
|
||||
# hostUrlPrefix is the host URL, but it is guaranteed to end
|
||||
# with a slash.
|
||||
self.hostUrlPrefix = hostUrl
|
||||
if self.hostUrlPrefix[-1] != '/':
|
||||
self.hostUrlPrefix += '/'
|
||||
|
||||
# downloadUrlPrefix is the URL prefix that should be used for
|
||||
# everything other than the contents.xml file. It might be
|
||||
# the same as hostUrlPrefix, but in the case of an
|
||||
# https-protected hostUrl, it will be the cleartext channel.
|
||||
self.downloadUrlPrefix = self.hostUrlPrefix
|
||||
|
||||
def freshenFile(self, http, fileSpec, localPathname):
|
||||
""" Ensures that the localPathname is the most current version
|
||||
of the file defined by fileSpec, as offered by host. If not,
|
||||
it downloads a new version on-the-spot. Returns true on
|
||||
success, false on failure. """
|
||||
|
||||
if fileSpec.quickVerify(pathname = localPathname):
|
||||
# It's good, keep it.
|
||||
return True
|
||||
|
||||
# It's stale, get a new one.
|
||||
doc = None
|
||||
if self.appRunner and self.appRunner.superMirrorUrl:
|
||||
# Use the "super mirror" first.
|
||||
url = core.URLSpec(self.appRunner.superMirrorUrl + fileSpec.filename)
|
||||
self.notify.info("Freshening %s" % (url))
|
||||
doc = http.getDocument(url)
|
||||
|
||||
if not doc or not doc.isValid():
|
||||
# Failing the super mirror, contact the actual host.
|
||||
url = core.URLSpec(self.hostUrlPrefix + fileSpec.filename)
|
||||
self.notify.info("Freshening %s" % (url))
|
||||
doc = http.getDocument(url)
|
||||
if not doc.isValid():
|
||||
return False
|
||||
|
||||
file = Filename.temporary('', 'p3d_')
|
||||
if not doc.downloadToFile(file):
|
||||
# Failed to download.
|
||||
file.unlink()
|
||||
return False
|
||||
|
||||
# Successfully downloaded!
|
||||
localPathname.makeDir()
|
||||
if not file.renameTo(localPathname):
|
||||
# Couldn't move it into place.
|
||||
file.unlink()
|
||||
return False
|
||||
|
||||
if not fileSpec.fullVerify(pathname = localPathname, notify = self.notify):
|
||||
# No good after download.
|
||||
self.notify.info("%s is still no good after downloading." % (url))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def downloadContentsFile(self, http, redownload = False,
|
||||
hashVal = None):
|
||||
""" Downloads the contents.xml file for this particular host,
|
||||
synchronously, and then reads it. Returns true on success,
|
||||
false on failure. If hashVal is not None, it should be a
|
||||
HashVal object, which will be filled with the hash from the
|
||||
new contents.xml file."""
|
||||
|
||||
if self.hasCurrentContentsFile():
|
||||
# We've already got one.
|
||||
return True
|
||||
|
||||
if self.appRunner and 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:
|
||||
# We start with the "super mirror", if it's defined.
|
||||
url = self.appRunner.superMirrorUrl + 'contents.xml'
|
||||
request = DocumentSpec(url)
|
||||
self.notify.info("Downloading contents file %s" % (request))
|
||||
|
||||
rf = Ramfile()
|
||||
channel = http.makeChannel(False)
|
||||
channel.getDocument(request)
|
||||
if not channel.downloadToRam(rf):
|
||||
self.notify.warning("Unable to download %s" % (url))
|
||||
rf = None
|
||||
|
||||
if not rf:
|
||||
# Then go to the main host, if our super mirror let us
|
||||
# down.
|
||||
|
||||
url = self.hostUrlPrefix + 'contents.xml'
|
||||
# Append a uniquifying query string to the URL to force the
|
||||
# download to go all the way through any caches. We use the
|
||||
# time in seconds; that's unique enough.
|
||||
url += '?' + str(int(time.time()))
|
||||
|
||||
# We might as well explicitly request the cache to be disabled
|
||||
# too, since we have an interface for that via HTTPChannel.
|
||||
request = DocumentSpec(url)
|
||||
request.setCacheControl(DocumentSpec.CCNoCache)
|
||||
|
||||
self.notify.info("Downloading contents file %s" % (request))
|
||||
statusCode = None
|
||||
statusString = ''
|
||||
for attempt in range(int(ConfigVariableInt('contents-xml-dl-attempts', 3))):
|
||||
if attempt > 0:
|
||||
self.notify.info("Retrying (%s)..."%(attempt,))
|
||||
rf = Ramfile()
|
||||
channel = http.makeChannel(False)
|
||||
channel.getDocument(request)
|
||||
if channel.downloadToRam(rf):
|
||||
self.notify.info("Successfully downloaded %s" % (url,))
|
||||
break
|
||||
else:
|
||||
rf = None
|
||||
statusCode = channel.getStatusCode()
|
||||
statusString = channel.getStatusString()
|
||||
self.notify.warning("Could not contact download server at %s" % (url,))
|
||||
self.notify.warning("Status code = %s %s" % (statusCode, statusString))
|
||||
|
||||
if not rf:
|
||||
self.notify.warning("Unable to download %s" % (url,))
|
||||
try:
|
||||
# Something screwed up.
|
||||
if statusCode == HTTPChannel.SCDownloadOpenError or \
|
||||
statusCode == HTTPChannel.SCDownloadWriteError:
|
||||
launcher.setPandaErrorCode(2)
|
||||
elif statusCode == 404:
|
||||
# 404 not found
|
||||
launcher.setPandaErrorCode(5)
|
||||
elif statusCode < 100:
|
||||
# statusCode < 100 implies the connection attempt itself
|
||||
# failed. This is usually due to firewall software
|
||||
# interfering. Apparently some firewall software might
|
||||
# allow the first connection and disallow subsequent
|
||||
# connections; how strange.
|
||||
launcher.setPandaErrorCode(4)
|
||||
else:
|
||||
# There are other kinds of failures, but these will
|
||||
# generally have been caught already by the first test; so
|
||||
# if we get here there may be some bigger problem. Just
|
||||
# give the generic "big problem" message.
|
||||
launcher.setPandaErrorCode(6)
|
||||
except NameError as e:
|
||||
# no launcher
|
||||
pass
|
||||
except AttributeError as e:
|
||||
self.notify.warning("%s" % (str(e),))
|
||||
pass
|
||||
return False
|
||||
|
||||
tempFilename = Filename.temporary('', 'p3d_', '.xml')
|
||||
if rf:
|
||||
f = open(tempFilename.toOsSpecific(), 'wb')
|
||||
f.write(rf.getData())
|
||||
f.close()
|
||||
if hashVal:
|
||||
hashVal.hashString(rf.getData())
|
||||
|
||||
if not self.readContentsFile(tempFilename, freshDownload = True):
|
||||
self.notify.warning("Failure reading %s" % (url))
|
||||
tempFilename.unlink()
|
||||
return False
|
||||
|
||||
tempFilename.unlink()
|
||||
return True
|
||||
|
||||
# Couldn't download the file. Maybe we should look for a
|
||||
# previously-downloaded copy already on disk?
|
||||
return False
|
||||
|
||||
def redownloadContentsFile(self, http):
|
||||
""" Downloads a new contents.xml file in case it has changed.
|
||||
Returns true if the file has indeed changed, false if it has
|
||||
not. """
|
||||
assert self.hasContentsFile
|
||||
|
||||
if self.appRunner and self.appRunner.verifyContents == self.appRunner.P3DVCNever:
|
||||
# Not allowed to.
|
||||
return False
|
||||
|
||||
url = self.hostUrlPrefix + 'contents.xml'
|
||||
self.notify.info("Redownloading %s" % (url))
|
||||
|
||||
# Get the hash of the original file.
|
||||
assert self.hostDir
|
||||
hv1 = HashVal()
|
||||
if self.contentsSpec.hash:
|
||||
hv1.setFromHex(self.contentsSpec.hash)
|
||||
else:
|
||||
filename = Filename(self.hostDir, 'contents.xml')
|
||||
hv1.hashFile(filename)
|
||||
|
||||
# Now download it again.
|
||||
self.hasContentsFile = False
|
||||
hv2 = HashVal()
|
||||
if not self.downloadContentsFile(http, redownload = True,
|
||||
hashVal = hv2):
|
||||
return False
|
||||
|
||||
if hv2 == HashVal():
|
||||
self.notify.info("%s didn't actually redownload." % (url))
|
||||
return False
|
||||
elif hv1 != hv2:
|
||||
self.notify.info("%s has changed." % (url))
|
||||
return True
|
||||
else:
|
||||
self.notify.info("%s has not changed." % (url))
|
||||
return False
|
||||
|
||||
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 \
|
||||
or self.appRunner.verifyContents == self.appRunner.P3DVCNever:
|
||||
# If we're not asking to verify contents, then
|
||||
# contents.xml files never expires.
|
||||
return self.hasContentsFile
|
||||
|
||||
now = int(time.time())
|
||||
return now < self.contentsExpiration and self.hasContentsFile
|
||||
|
||||
def readContentsFile(self, tempFilename = None, freshDownload = False):
|
||||
""" Reads the contents.xml file for this particular host, once
|
||||
it has been downloaded into the indicated temporary file.
|
||||
Returns true on success, false if the contents file is not
|
||||
already on disk or is unreadable.
|
||||
|
||||
If tempFilename is specified, it is the filename read, and it
|
||||
is copied the file into the standard location if it's not
|
||||
there already. If tempFilename is not specified, the standard
|
||||
filename is read if it is known. """
|
||||
|
||||
if not hasattr(core, 'TiXmlDocument'):
|
||||
return False
|
||||
|
||||
if not tempFilename:
|
||||
if self.hostDir:
|
||||
# If the filename is not specified, we can infer it
|
||||
# if we already know our hostDir
|
||||
hostDir = self.hostDir
|
||||
else:
|
||||
# Otherwise, we have to guess the hostDir.
|
||||
hostDir = self.__determineHostDir(None, self.hostUrl)
|
||||
|
||||
tempFilename = Filename(hostDir, 'contents.xml')
|
||||
|
||||
doc = core.TiXmlDocument(tempFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
return False
|
||||
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if not xcontents:
|
||||
return False
|
||||
|
||||
maxAge = xcontents.Attribute('max_age')
|
||||
if maxAge:
|
||||
try:
|
||||
maxAge = int(maxAge)
|
||||
except:
|
||||
maxAge = None
|
||||
if maxAge is None:
|
||||
# Default max_age if unspecified (see p3d_plugin.h).
|
||||
from direct.p3d.AppRunner import AppRunner
|
||||
maxAge = AppRunner.P3D_CONTENTS_DEFAULT_MAX_AGE
|
||||
|
||||
# Get the latest possible expiration time, based on the max_age
|
||||
# indication. Any expiration time later than this is in error.
|
||||
now = int(time.time())
|
||||
self.contentsExpiration = now + maxAge
|
||||
|
||||
if freshDownload:
|
||||
self.contentsSpec.readHash(tempFilename)
|
||||
|
||||
# Update the XML with the new download information.
|
||||
xorig = xcontents.FirstChildElement('orig')
|
||||
while xorig:
|
||||
xcontents.RemoveChild(xorig)
|
||||
xorig = xcontents.FirstChildElement('orig')
|
||||
|
||||
xorig = core.TiXmlElement('orig')
|
||||
self.contentsSpec.storeXml(xorig)
|
||||
xorig.SetAttribute('expiration', str(self.contentsExpiration))
|
||||
|
||||
xcontents.InsertEndChild(xorig)
|
||||
|
||||
else:
|
||||
# Read the download hash and expiration time from the XML.
|
||||
expiration = None
|
||||
xorig = xcontents.FirstChildElement('orig')
|
||||
if xorig:
|
||||
self.contentsSpec.loadXml(xorig)
|
||||
expiration = xorig.Attribute('expiration')
|
||||
if expiration:
|
||||
try:
|
||||
expiration = int(expiration)
|
||||
except:
|
||||
expiration = None
|
||||
if not self.contentsSpec.hash:
|
||||
self.contentsSpec.readHash(tempFilename)
|
||||
|
||||
if expiration is not None:
|
||||
self.contentsExpiration = min(self.contentsExpiration, expiration)
|
||||
|
||||
# Look for our own entry in the hosts table.
|
||||
if self.hostUrl:
|
||||
self.__findHostXml(xcontents)
|
||||
else:
|
||||
assert self.hostDir
|
||||
self.__findHostXmlForHostDir(xcontents)
|
||||
|
||||
if self.rootDir and not self.hostDir:
|
||||
self.hostDir = self.__determineHostDir(None, self.hostUrl)
|
||||
|
||||
# Get the list of packages available for download and/or import.
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
name = xpackage.Attribute('name')
|
||||
platform = xpackage.Attribute('platform')
|
||||
version = xpackage.Attribute('version')
|
||||
try:
|
||||
solo = int(xpackage.Attribute('solo') or '')
|
||||
except ValueError:
|
||||
solo = False
|
||||
try:
|
||||
perPlatform = int(xpackage.Attribute('per_platform') or '')
|
||||
except ValueError:
|
||||
perPlatform = False
|
||||
|
||||
package = self.__makePackage(name, platform, version, solo, perPlatform)
|
||||
package.descFile = FileSpec()
|
||||
package.descFile.loadXml(xpackage)
|
||||
package.setupFilenames()
|
||||
|
||||
package.importDescFile = None
|
||||
ximport = xpackage.FirstChildElement('import')
|
||||
if ximport:
|
||||
package.importDescFile = FileSpec()
|
||||
package.importDescFile.loadXml(ximport)
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
self.hasContentsFile = True
|
||||
|
||||
# Now save the contents.xml file into the standard location.
|
||||
if self.appRunner and 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
|
||||
|
||||
def __findHostXml(self, xcontents):
|
||||
""" Looks for the <host> or <alt_host> entry in the
|
||||
contents.xml that corresponds to the URL that we actually
|
||||
downloaded from. """
|
||||
|
||||
xhost = xcontents.FirstChildElement('host')
|
||||
while xhost:
|
||||
url = xhost.Attribute('url')
|
||||
if url == self.hostUrl:
|
||||
self.readHostXml(xhost)
|
||||
return
|
||||
|
||||
xalthost = xhost.FirstChildElement('alt_host')
|
||||
while xalthost:
|
||||
url = xalthost.Attribute('url')
|
||||
if url == self.hostUrl:
|
||||
self.readHostXml(xalthost)
|
||||
return
|
||||
xalthost = xalthost.NextSiblingElement('alt_host')
|
||||
|
||||
xhost = xhost.NextSiblingElement('host')
|
||||
|
||||
def __findHostXmlForHostDir(self, xcontents):
|
||||
""" Looks for the <host> or <alt_host> entry in the
|
||||
contents.xml that corresponds to the host dir that we read the
|
||||
contents.xml from. This is used when reading a contents.xml
|
||||
file found on disk, as opposed to downloading it from a
|
||||
site. """
|
||||
|
||||
xhost = xcontents.FirstChildElement('host')
|
||||
while xhost:
|
||||
url = xhost.Attribute('url')
|
||||
hostDirBasename = xhost.Attribute('host_dir')
|
||||
hostDir = self.__determineHostDir(hostDirBasename, url)
|
||||
if hostDir == self.hostDir:
|
||||
self.__setHostUrl(url)
|
||||
self.readHostXml(xhost)
|
||||
return
|
||||
|
||||
xalthost = xhost.FirstChildElement('alt_host')
|
||||
while xalthost:
|
||||
url = xalthost.Attribute('url')
|
||||
hostDirBasename = xalthost.Attribute('host_dir')
|
||||
hostDir = self.__determineHostDir(hostDirBasename, url)
|
||||
if hostDir == self.hostDir:
|
||||
self.__setHostUrl(url)
|
||||
self.readHostXml(xalthost)
|
||||
return
|
||||
xalthost = xalthost.NextSiblingElement('alt_host')
|
||||
|
||||
xhost = xhost.NextSiblingElement('host')
|
||||
|
||||
def readHostXml(self, xhost):
|
||||
""" Reads a <host> or <alt_host> entry and applies the data to
|
||||
this object. """
|
||||
|
||||
descriptiveName = xhost.Attribute('descriptive_name')
|
||||
if descriptiveName and not self.descriptiveName:
|
||||
self.descriptiveName = descriptiveName
|
||||
|
||||
hostDirBasename = xhost.Attribute('host_dir')
|
||||
if self.rootDir and not self.hostDir:
|
||||
self.hostDir = self.__determineHostDir(hostDirBasename, self.hostUrl)
|
||||
|
||||
# Get the "download" URL, which is the source from which we
|
||||
# download everything other than the contents.xml file.
|
||||
downloadUrl = xhost.Attribute('download_url')
|
||||
if downloadUrl:
|
||||
self.downloadUrlPrefix = downloadUrl
|
||||
if self.downloadUrlPrefix[-1] != '/':
|
||||
self.downloadUrlPrefix += '/'
|
||||
else:
|
||||
self.downloadUrlPrefix = self.hostUrlPrefix
|
||||
|
||||
xmirror = xhost.FirstChildElement('mirror')
|
||||
while xmirror:
|
||||
url = xmirror.Attribute('url')
|
||||
if url:
|
||||
if url[-1] != '/':
|
||||
url += '/'
|
||||
if url not in self.mirrors:
|
||||
self.mirrors.append(url)
|
||||
xmirror = xmirror.NextSiblingElement('mirror')
|
||||
|
||||
xalthost = xhost.FirstChildElement('alt_host')
|
||||
while xalthost:
|
||||
keyword = xalthost.Attribute('keyword')
|
||||
url = xalthost.Attribute('url')
|
||||
if url and keyword:
|
||||
self.altHosts[keyword] = url
|
||||
xalthost = xalthost.NextSiblingElement('alt_host')
|
||||
|
||||
def __makePackage(self, name, platform, version, solo, perPlatform):
|
||||
""" Creates a new PackageInfo entry for the given name,
|
||||
version, and platform. If there is already a matching
|
||||
PackageInfo, returns it. """
|
||||
|
||||
if not platform:
|
||||
platform = None
|
||||
|
||||
platforms = self.packages.setdefault((name, version or ""), {})
|
||||
package = platforms.get("", None)
|
||||
if not package:
|
||||
package = PackageInfo(self, name, version, platform = platform,
|
||||
solo = solo, asMirror = self.asMirror,
|
||||
perPlatform = perPlatform)
|
||||
platforms[platform or ""] = package
|
||||
|
||||
return package
|
||||
|
||||
def getPackage(self, name, version, platform = None):
|
||||
""" Returns a PackageInfo that matches the indicated name and
|
||||
version and the indicated platform or the current runtime
|
||||
platform, if one is provided by this host, or None if not. """
|
||||
|
||||
assert self.hasContentsFile
|
||||
platforms = self.packages.get((name, version or ""), {})
|
||||
|
||||
if platform:
|
||||
# In this case, we are looking for a specific platform
|
||||
# only.
|
||||
return platforms.get(platform, None)
|
||||
|
||||
# We are looking for one matching the current runtime
|
||||
# platform. First, look for a package matching the current
|
||||
# platform exactly.
|
||||
package = platforms.get(PandaSystem.getPlatform(), None)
|
||||
|
||||
# If not found, look for one matching no particular platform.
|
||||
if not package:
|
||||
package = platforms.get("", None)
|
||||
|
||||
return package
|
||||
|
||||
def getPackages(self, name = None, platform = None):
|
||||
""" Returns a list of PackageInfo objects that match the
|
||||
indicated name and/or platform, with no particular regards to
|
||||
version. If name is None, all packages are returned. """
|
||||
|
||||
assert self.hasContentsFile
|
||||
|
||||
packages = []
|
||||
for (pn, version), platforms in self.packages.items():
|
||||
if name and pn != name:
|
||||
continue
|
||||
|
||||
if not platform:
|
||||
for p2 in platforms:
|
||||
package = self.getPackage(pn, version, platform = p2)
|
||||
if package:
|
||||
packages.append(package)
|
||||
else:
|
||||
package = self.getPackage(pn, version, platform = platform)
|
||||
if package:
|
||||
packages.append(package)
|
||||
|
||||
return packages
|
||||
|
||||
def getAllPackages(self, includeAllPlatforms = False):
|
||||
""" Returns a list of all available packages provided by this
|
||||
host. """
|
||||
|
||||
result = []
|
||||
|
||||
items = sorted(self.packages.items())
|
||||
for key, platforms in items:
|
||||
if self.perPlatform or includeAllPlatforms:
|
||||
# If we maintain a different answer per platform,
|
||||
# return all of them.
|
||||
pitems = sorted(platforms.items())
|
||||
for pkey, package in pitems:
|
||||
result.append(package)
|
||||
else:
|
||||
# If we maintain a host for the current platform
|
||||
# only (e.g. a client copy), then return only the
|
||||
# current platform, or no particular platform.
|
||||
package = platforms.get(PandaSystem.getPlatform(), None)
|
||||
if not package:
|
||||
package = platforms.get("", None)
|
||||
|
||||
if package:
|
||||
result.append(package)
|
||||
|
||||
return result
|
||||
|
||||
def deletePackages(self, packages):
|
||||
""" Removes all of the indicated packages from the disk,
|
||||
uninstalling them and deleting all of their files. The
|
||||
packages parameter must be a list of one or more PackageInfo
|
||||
objects, for instance as returned by getPackage(). Returns
|
||||
the list of packages that were NOT found. """
|
||||
|
||||
packages = packages[:]
|
||||
|
||||
for key, platforms in list(self.packages.items()):
|
||||
for platform, package in list(platforms.items()):
|
||||
if package in packages:
|
||||
self.__deletePackageFiles(package)
|
||||
del platforms[platform]
|
||||
packages.remove(package)
|
||||
|
||||
if not platforms:
|
||||
# If we've removed all the platforms for a given
|
||||
# package, remove the key from the toplevel map.
|
||||
del self.packages[key]
|
||||
|
||||
return packages
|
||||
|
||||
def __deletePackageFiles(self, package):
|
||||
""" Called by deletePackage(), this actually removes the files
|
||||
for the indicated package. """
|
||||
|
||||
if self.appRunner:
|
||||
self.notify.info("Deleting package %s: %s" % (package.packageName, package.getPackageDir()))
|
||||
self.appRunner.rmtree(package.getPackageDir())
|
||||
|
||||
self.appRunner.sendRequest('forget_package', self.hostUrl, package.packageName, package.packageVersion or '')
|
||||
|
||||
def __determineHostDir(self, hostDirBasename, hostUrl):
|
||||
""" Hashes the host URL into a (mostly) unique directory
|
||||
string, which will be the root of the host's install tree.
|
||||
Returns the resulting path, as a Filename.
|
||||
|
||||
This code is duplicated in C++, in
|
||||
P3DHost::determine_host_dir(). """
|
||||
|
||||
if hostDirBasename:
|
||||
# If the contents.xml specified a host_dir parameter, use
|
||||
# it.
|
||||
hostDir = str(self.rootDir) + '/hosts'
|
||||
for component in hostDirBasename.split('/'):
|
||||
if component:
|
||||
if component[0] == '.':
|
||||
# Forbid ".foo" or "..".
|
||||
component = 'x' + component
|
||||
hostDir += '/'
|
||||
hostDir += component
|
||||
return Filename(hostDir)
|
||||
|
||||
hostDir = 'hosts/'
|
||||
|
||||
# Look for a server name in the URL. Including this string in the
|
||||
# directory name makes it friendlier for people browsing the
|
||||
# directory.
|
||||
|
||||
# We could use URLSpec, but we do it by hand instead, to make
|
||||
# it more likely that our hash code will exactly match the
|
||||
# similar logic in P3DHost.
|
||||
p = hostUrl.find('://')
|
||||
hostname = ''
|
||||
if p != -1:
|
||||
start = p + 3
|
||||
end = hostUrl.find('/', start)
|
||||
# Now start .. end is something like "username@host:port".
|
||||
|
||||
at = hostUrl.find('@', start)
|
||||
if at != -1 and at < end:
|
||||
start = at + 1
|
||||
|
||||
colon = hostUrl.find(':', start)
|
||||
if colon != -1 and colon < end:
|
||||
end = colon
|
||||
|
||||
# Now start .. end is just the hostname.
|
||||
hostname = hostUrl[start : end]
|
||||
|
||||
# Now build a hash string of the whole URL. We'll use MD5 to
|
||||
# get a pretty good hash, with a minimum chance of collision.
|
||||
# Even if there is a hash collision, though, it's not the end
|
||||
# of the world; it just means that both hosts will dump their
|
||||
# packages into the same directory, and they'll fight over the
|
||||
# toplevel contents.xml file. Assuming they use different
|
||||
# version numbers (which should be safe since they have the
|
||||
# same hostname), there will be minimal redownloading.
|
||||
|
||||
hashSize = 16
|
||||
keepHash = hashSize
|
||||
if hostname:
|
||||
hostDir += hostname + '_'
|
||||
|
||||
# If we successfully got a hostname, we don't really need the
|
||||
# full hash. We'll keep half of it.
|
||||
keepHash = keepHash // 2
|
||||
|
||||
md = HashVal()
|
||||
md.hashString(hostUrl)
|
||||
hostDir += md.asHex()[:keepHash * 2]
|
||||
|
||||
hostDir = Filename(self.rootDir, hostDir)
|
||||
return hostDir
|
||||
@@ -1,24 +0,0 @@
|
||||
__all__ = ["InstalledHostData"]
|
||||
|
||||
from panda3d.core import URLSpec
|
||||
|
||||
class InstalledHostData:
|
||||
""" A list of instances of this class is returned by
|
||||
AppRunner.scanInstalledPackages(). Each of these corresponds to a
|
||||
particular host that has provided packages that have been
|
||||
installed on the local client. """
|
||||
|
||||
def __init__(self, host, dirnode):
|
||||
self.host = host
|
||||
self.pathname = dirnode.pathname
|
||||
self.totalSize = dirnode.getTotalSize()
|
||||
self.packages = []
|
||||
|
||||
if self.host:
|
||||
self.hostUrl = self.host.hostUrl
|
||||
self.descriptiveName = self.host.descriptiveName
|
||||
if not self.descriptiveName:
|
||||
self.descriptiveName = URLSpec(self.hostUrl).getServer()
|
||||
else:
|
||||
self.hostUrl = 'unknown'
|
||||
self.descriptiveName = 'unknown'
|
||||
@@ -1,30 +0,0 @@
|
||||
__all__ = ["InstalledPackageData"]
|
||||
|
||||
class InstalledPackageData:
|
||||
""" A list of instances of this class is maintained by
|
||||
InstalledHostData (which is in turn returned by
|
||||
AppRunner.scanInstalledPackages()). Each of these corresponds to
|
||||
a particular package that has been installed on the local
|
||||
client. """
|
||||
|
||||
def __init__(self, package, dirnode):
|
||||
self.package = package
|
||||
self.pathname = dirnode.pathname
|
||||
self.totalSize = dirnode.getTotalSize()
|
||||
self.lastUse = None
|
||||
|
||||
if self.package:
|
||||
self.displayName = self.package.getFormattedName()
|
||||
xusage = self.package.getUsage()
|
||||
|
||||
if xusage:
|
||||
lastUse = xusage.Attribute('last_use')
|
||||
try:
|
||||
lastUse = int(lastUse or '')
|
||||
except ValueError:
|
||||
lastUse = None
|
||||
self.lastUse = lastUse
|
||||
|
||||
else:
|
||||
self.displayName = dirnode.pathname.getBasename()
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
""" This module defines some simple classes and instances which are
|
||||
useful when writing code that integrates with JavaScript, especially
|
||||
code that runs in a browser via the web plugin. """
|
||||
|
||||
__all__ = ["UndefinedObject", "Undefined", "ConcreteStruct", "BrowserObject", "MethodWrapper"]
|
||||
|
||||
class UndefinedObject:
|
||||
""" This is a special object that is returned by the browser to
|
||||
represent an "undefined" or "void" value, typically the value for
|
||||
an uninitialized variable or undefined property. It has no
|
||||
attributes, similar to None, but it is a slightly different
|
||||
concept in JavaScript. """
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
||||
def __str__(self):
|
||||
return "Undefined"
|
||||
|
||||
# In fact, we normally always return this precise instance of the
|
||||
# UndefinedObject.
|
||||
Undefined = UndefinedObject()
|
||||
|
||||
class ConcreteStruct:
|
||||
""" Python objects that inherit from this class are passed to
|
||||
JavaScript as a concrete struct: a mapping from string -> value,
|
||||
with no methods, passed by value. This can be more optimal than
|
||||
traditional Python objects which are passed by reference,
|
||||
especially for small objects which might be repeatedly referenced
|
||||
on the JavaScript side. """
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def getConcreteProperties(self):
|
||||
""" Returns a list of 2-tuples of the (key, value) pairs that
|
||||
are to be passed to the concrete instance. By default, this
|
||||
returns all properties of the object. You can override this
|
||||
to restrict the set of properties that are uploaded. """
|
||||
|
||||
return list(self.__dict__.items())
|
||||
|
||||
class BrowserObject:
|
||||
""" This class provides the Python wrapper around some object that
|
||||
actually exists in the plugin host's namespace, e.g. a JavaScript
|
||||
or DOM object. """
|
||||
|
||||
def __init__(self, runner, objectId):
|
||||
self.__dict__['_BrowserObject__runner'] = runner
|
||||
self.__dict__['_BrowserObject__objectId'] = objectId
|
||||
|
||||
# This element is filled in by __getattr__; it connects
|
||||
# the object to its parent.
|
||||
self.__dict__['_BrowserObject__childObject'] = (None, None)
|
||||
|
||||
# This is a cache of method names to MethodWrapper objects in
|
||||
# the parent object.
|
||||
self.__dict__['_BrowserObject__methods'] = {}
|
||||
|
||||
def __del__(self):
|
||||
# When the BrowserObject destructs, tell the parent process it
|
||||
# doesn't need to keep around its corresponding P3D_object any
|
||||
# more.
|
||||
self.__runner.dropObject(self.__objectId)
|
||||
|
||||
def __cacheMethod(self, methodName):
|
||||
""" Stores a pointer to the named method on this object, so
|
||||
that the next time __getattr__ is called, it can retrieve the
|
||||
method wrapper without having to query the browser. This
|
||||
cache assumes that callable methods don't generally come and
|
||||
go on and object.
|
||||
|
||||
The return value is the MethodWrapper object. """
|
||||
|
||||
method = self.__methods.get(methodName, None)
|
||||
if method is None:
|
||||
method = MethodWrapper(self.__runner, self, methodName)
|
||||
self.__methods[methodName] = method
|
||||
return method
|
||||
|
||||
def __str__(self):
|
||||
return self.toString()
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
needsResponse = True
|
||||
if 'needsResponse' in kw:
|
||||
needsResponse = kw['needsResponse']
|
||||
del kw['needsResponse']
|
||||
if kw:
|
||||
raise ArgumentError('Keyword arguments not supported')
|
||||
|
||||
try:
|
||||
parentObj, attribName = self.__childObject
|
||||
if parentObj:
|
||||
# Call it as a method.
|
||||
if parentObj is self.__runner.dom and attribName == 'alert':
|
||||
# As a special hack, we don't wait for the return
|
||||
# value from the alert() call, since this is a
|
||||
# blocking call, and waiting for this could cause
|
||||
# problems.
|
||||
needsResponse = False
|
||||
|
||||
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], str):
|
||||
# As another special hack, we make dom.eval() a
|
||||
# special case, and map it directly into an eval()
|
||||
# call. If the string begins with 'void ', we further
|
||||
# assume we're not waiting for a response.
|
||||
if args[0].startswith('void '):
|
||||
needsResponse = False
|
||||
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
|
||||
else:
|
||||
# This is a normal method call.
|
||||
try:
|
||||
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Problem on the call. Maybe no such method?
|
||||
raise AttributeError
|
||||
|
||||
# Hey, the method call appears to have succeeded.
|
||||
# Cache the method object on the parent so we won't
|
||||
# have to look up the method wrapper again next time.
|
||||
parentObj.__cacheMethod(attribName)
|
||||
|
||||
else:
|
||||
# Call it as a plain function.
|
||||
result = self.__runner.scriptRequest('call', self, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Some odd problem on the call.
|
||||
raise TypeError
|
||||
|
||||
return result
|
||||
|
||||
def __getattr__(self, name):
|
||||
""" Remaps attempts to query an attribute, as in obj.attr,
|
||||
into the appropriate calls to query the actual browser object
|
||||
under the hood. """
|
||||
|
||||
# First check to see if there's a cached method wrapper from a
|
||||
# previous call.
|
||||
method = self.__methods.get(name, None)
|
||||
if method:
|
||||
return method
|
||||
|
||||
# No cache. Go query the browser for the desired value.
|
||||
try:
|
||||
value = self.__runner.scriptRequest('get_property', self,
|
||||
propertyName = name)
|
||||
except EnvironmentError:
|
||||
# Failed to retrieve the attribute. But maybe there's a
|
||||
# method instead?
|
||||
if self.__runner.scriptRequest('has_method', self, propertyName = name):
|
||||
# Yes, so create a method wrapper for it.
|
||||
return self.__cacheMethod(name)
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
if isinstance(value, BrowserObject):
|
||||
# Fill in the parent object association, so __call__ can
|
||||
# properly call a method. (Javascript needs to know the
|
||||
# method container at the time of the call, and doesn't
|
||||
# store it on the function object.)
|
||||
value.__dict__['_BrowserObject__childObject'] = (self, name)
|
||||
|
||||
return value
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.__dict__:
|
||||
self.__dict__[name] = value
|
||||
return
|
||||
|
||||
result = self.__runner.scriptRequest('set_property', self,
|
||||
propertyName = name,
|
||||
value = value)
|
||||
if not result:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __delattr__(self, name):
|
||||
if name in self.__dict__:
|
||||
del self.__dict__[name]
|
||||
return
|
||||
|
||||
result = self.__runner.scriptRequest('del_property', self,
|
||||
propertyName = name)
|
||||
if not result:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Remaps attempts to query an attribute, as in obj['attr'],
|
||||
into the appropriate calls to query the actual browser object
|
||||
under the hood. Following the JavaScript convention, we treat
|
||||
obj['attr'] almost the same as obj.attr. """
|
||||
|
||||
try:
|
||||
value = self.__runner.scriptRequest('get_property', self,
|
||||
propertyName = str(key))
|
||||
except EnvironmentError:
|
||||
# Failed to retrieve the property. We return IndexError
|
||||
# for numeric keys so we can properly support Python's
|
||||
# iterators, but we return KeyError for string keys to
|
||||
# emulate mapping objects.
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
else:
|
||||
raise IndexError(key)
|
||||
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
result = self.__runner.scriptRequest('set_property', self,
|
||||
propertyName = str(key),
|
||||
value = value)
|
||||
if not result:
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
else:
|
||||
raise IndexError(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
result = self.__runner.scriptRequest('del_property', self,
|
||||
propertyName = str(key))
|
||||
if not result:
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
else:
|
||||
raise IndexError(key)
|
||||
|
||||
class MethodWrapper:
|
||||
""" This is a Python wrapper around a property of a BrowserObject
|
||||
that doesn't appear to be a first-class object in the Python
|
||||
sense, but is nonetheless a callable method. """
|
||||
|
||||
def __init__(self, runner, parentObj, objectId):
|
||||
self.__dict__['_MethodWrapper__runner'] = runner
|
||||
self.__dict__['_MethodWrapper__childObject'] = (parentObj, objectId)
|
||||
|
||||
def __str__(self):
|
||||
parentObj, attribName = self.__childObject
|
||||
return "%s.%s" % (parentObj, attribName)
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
needsResponse = True
|
||||
if 'needsResponse' in kw:
|
||||
needsResponse = kw['needsResponse']
|
||||
del kw['needsResponse']
|
||||
if kw:
|
||||
raise ArgumentError('Keyword arguments not supported')
|
||||
|
||||
try:
|
||||
parentObj, attribName = self.__childObject
|
||||
# Call it as a method.
|
||||
if parentObj is self.__runner.dom and attribName == 'alert':
|
||||
# As a special hack, we don't wait for the return
|
||||
# value from the alert() call, since this is a
|
||||
# blocking call, and waiting for this could cause
|
||||
# problems.
|
||||
needsResponse = False
|
||||
|
||||
if parentObj is self.__runner.dom and attribName == 'eval' and len(args) == 1 and isinstance(args[0], str):
|
||||
# As another special hack, we make dom.eval() a
|
||||
# special case, and map it directly into an eval()
|
||||
# call. If the string begins with 'void ', we further
|
||||
# assume we're not waiting for a response.
|
||||
if args[0].startswith('void '):
|
||||
needsResponse = False
|
||||
result = self.__runner.scriptRequest('eval', parentObj, value = args[0], needsResponse = needsResponse)
|
||||
else:
|
||||
# This is a normal method call.
|
||||
try:
|
||||
result = self.__runner.scriptRequest('call', parentObj, propertyName = attribName, value = args, needsResponse = needsResponse)
|
||||
except EnvironmentError:
|
||||
# Problem on the call. Maybe no such method?
|
||||
raise AttributeError
|
||||
|
||||
except EnvironmentError:
|
||||
# Some odd problem on the call.
|
||||
raise TypeError
|
||||
|
||||
return result
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
""" setattr will generally fail on method objects. """
|
||||
raise AttributeError(name)
|
||||
|
||||
def __delattr__(self, name):
|
||||
""" delattr will generally fail on method objects. """
|
||||
raise AttributeError(name)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,640 +0,0 @@
|
||||
__all__ = ["PackageInstaller"]
|
||||
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.stdpy.threading import Lock, RLock
|
||||
from direct.showbase.MessengerGlobal import messenger
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.p3d.PackageInfo import PackageInfo
|
||||
from panda3d.core import TPLow, PStatCollector
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
|
||||
class PackageInstaller(DirectObject):
|
||||
|
||||
""" This class is used in a p3d runtime environment to manage the
|
||||
asynchronous download and installation of packages. If you just
|
||||
want to install a package synchronously, see
|
||||
appRunner.installPackage() for a simpler interface.
|
||||
|
||||
To use this class, you should subclass from it and override any of
|
||||
the six callback methods: downloadStarted(), packageStarted(),
|
||||
packageProgress(), downloadProgress(), packageFinished(),
|
||||
downloadFinished().
|
||||
|
||||
Also see DWBPackageInstaller, which does exactly this, to add a
|
||||
DirectWaitBar GUI.
|
||||
|
||||
"""
|
||||
|
||||
notify = directNotify.newCategory("PackageInstaller")
|
||||
|
||||
globalLock = Lock()
|
||||
nextUniqueId = 1
|
||||
|
||||
# This is a chain of state values progressing forward in time.
|
||||
S_initial = 0 # addPackage() calls are being made
|
||||
S_ready = 1 # donePackages() has been called
|
||||
S_started = 2 # download has started
|
||||
S_done = 3 # download is over
|
||||
|
||||
class PendingPackage:
|
||||
""" This class describes a package added to the installer for
|
||||
download. """
|
||||
|
||||
notify = directNotify.newCategory("PendingPackage")
|
||||
|
||||
def __init__(self, packageName, version, host):
|
||||
self.packageName = packageName
|
||||
self.version = version
|
||||
self.host = host
|
||||
|
||||
# This will be filled in properly by checkDescFile() or
|
||||
# getDescFile(); in the meantime, set a placeholder.
|
||||
self.package = PackageInfo(host, packageName, version)
|
||||
|
||||
# Set true when the package has finished downloading,
|
||||
# either successfully or unsuccessfully.
|
||||
self.done = False
|
||||
|
||||
# Set true or false when self.done has been set.
|
||||
self.success = False
|
||||
|
||||
# Set true when the packageFinished() callback has been
|
||||
# delivered.
|
||||
self.notified = False
|
||||
|
||||
# These are used to ensure the callbacks only get
|
||||
# delivered once for a particular package.
|
||||
self.calledPackageStarted = False
|
||||
self.calledPackageFinished = False
|
||||
|
||||
# This is the amount of stuff we have to process to
|
||||
# install this package, and the amount of stuff we have
|
||||
# processed so far. "Stuff" includes bytes downloaded,
|
||||
# bytes uncompressed, and bytes extracted; and each of
|
||||
# which is weighted differently into one grand total. So,
|
||||
# the total doesn't really represent bytes; it's a
|
||||
# unitless number, which means something only as a ratio
|
||||
# to other packages. Filled in by checkDescFile() or
|
||||
# getDescFile().
|
||||
self.downloadEffort = 0
|
||||
|
||||
# Similar, but this is the theoretical effort if the
|
||||
# package were already downloaded.
|
||||
self.prevDownloadedEffort = 0
|
||||
|
||||
def __cmp__(self, pp):
|
||||
""" Python comparision function. This makes all
|
||||
PendingPackages withe same (packageName, version, host)
|
||||
combination be deemed equivalent. """
|
||||
return cmp((self.packageName, self.version, self.host),
|
||||
(pp.packageName, pp.version, pp.host))
|
||||
|
||||
def getProgress(self):
|
||||
""" Returns the download progress of this package in the
|
||||
range 0..1. """
|
||||
|
||||
return self.package.downloadProgress
|
||||
|
||||
def checkDescFile(self):
|
||||
""" Returns true if the desc file is already downloaded
|
||||
and good, or false if it needs to be downloaded. """
|
||||
|
||||
if not self.host.hasCurrentContentsFile():
|
||||
# If the contents file isn't ready yet, we can't check
|
||||
# the desc file yet.
|
||||
return False
|
||||
|
||||
# All right, get the package info now.
|
||||
package = self.host.getPackage(self.packageName, self.version)
|
||||
if not package:
|
||||
self.notify.warning("Package %s %s not known on %s" % (
|
||||
self.packageName, self.version, self.host.hostUrl))
|
||||
return False
|
||||
|
||||
self.package = package
|
||||
self.package.checkStatus()
|
||||
|
||||
if not self.package.hasDescFile:
|
||||
return False
|
||||
|
||||
self.downloadEffort = self.package.getDownloadEffort()
|
||||
self.prevDownloadEffort = 0
|
||||
if self.downloadEffort == 0:
|
||||
self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def getDescFile(self, http):
|
||||
""" Synchronously downloads the desc files required for
|
||||
the package. """
|
||||
|
||||
if not self.host.downloadContentsFile(http):
|
||||
return False
|
||||
|
||||
# All right, get the package info now.
|
||||
package = self.host.getPackage(self.packageName, self.version)
|
||||
if not package:
|
||||
self.notify.warning("Package %s %s not known on %s" % (
|
||||
self.packageName, self.version, self.host.hostUrl))
|
||||
return False
|
||||
|
||||
self.package = package
|
||||
if not self.package.downloadDescFile(http):
|
||||
return False
|
||||
|
||||
self.package.checkStatus()
|
||||
self.downloadEffort = self.package.getDownloadEffort()
|
||||
self.prevDownloadEffort = 0
|
||||
if self.downloadEffort == 0:
|
||||
self.prevDownloadedEffort = self.package.getPrevDownloadedEffort()
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self, appRunner, taskChain = 'default'):
|
||||
self.globalLock.acquire()
|
||||
try:
|
||||
self.uniqueId = PackageInstaller.nextUniqueId
|
||||
PackageInstaller.nextUniqueId += 1
|
||||
finally:
|
||||
self.globalLock.release()
|
||||
|
||||
self.appRunner = appRunner
|
||||
self.taskChain = taskChain
|
||||
|
||||
# If we're to be running on an asynchronous task chain, and
|
||||
# the task chain hasn't yet been set up already, create the
|
||||
# default parameters now.
|
||||
if taskChain != 'default' and not taskMgr.hasTaskChain(self.taskChain):
|
||||
taskMgr.setupTaskChain(self.taskChain, numThreads = 1,
|
||||
threadPriority = TPLow)
|
||||
|
||||
self.callbackLock = Lock()
|
||||
self.calledDownloadStarted = False
|
||||
self.calledDownloadFinished = False
|
||||
|
||||
# A list of all packages that have been added to the
|
||||
# installer.
|
||||
self.packageLock = RLock()
|
||||
self.packages = []
|
||||
self.state = self.S_initial
|
||||
|
||||
# A list of packages that are waiting for their desc files.
|
||||
self.needsDescFile = []
|
||||
self.descFileTask = None
|
||||
|
||||
# A list of packages that are waiting to be downloaded and
|
||||
# installed.
|
||||
self.needsDownload = []
|
||||
self.downloadTask = None
|
||||
|
||||
# A list of packages that were already done at the time they
|
||||
# were passed to addPackage().
|
||||
self.earlyDone = []
|
||||
|
||||
# A list of packages that have been successfully installed, or
|
||||
# packages that have failed.
|
||||
self.done = []
|
||||
self.failed = []
|
||||
|
||||
# This task is spawned on the default task chain, to update
|
||||
# the status during the download.
|
||||
self.progressTask = None
|
||||
|
||||
self.accept('PackageInstaller-%s-allHaveDesc' % self.uniqueId,
|
||||
self.__allHaveDesc)
|
||||
self.accept('PackageInstaller-%s-packageStarted' % self.uniqueId,
|
||||
self.__packageStarted)
|
||||
self.accept('PackageInstaller-%s-packageDone' % self.uniqueId,
|
||||
self.__packageDone)
|
||||
|
||||
def destroy(self):
|
||||
""" Interrupts all pending downloads. No further callbacks
|
||||
will be made. """
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
""" Interrupts all pending downloads. No further callbacks
|
||||
will be made. """
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
if self.descFileTask:
|
||||
taskMgr.remove(self.descFileTask)
|
||||
self.descFileTask = None
|
||||
if self.downloadTask:
|
||||
taskMgr.remove(self.downloadTask)
|
||||
self.downloadTask = None
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if self.progressTask:
|
||||
taskMgr.remove(self.progressTask)
|
||||
self.progressTask = None
|
||||
|
||||
self.ignoreAll()
|
||||
|
||||
def addPackage(self, packageName, version = None, hostUrl = None):
|
||||
""" Adds the named package to the list of packages to be
|
||||
downloaded. Call donePackages() to finish the list. """
|
||||
|
||||
if self.state != self.S_initial:
|
||||
raise ValueError('addPackage called after donePackages')
|
||||
|
||||
host = self.appRunner.getHostWithAlt(hostUrl)
|
||||
pp = self.PendingPackage(packageName, version, host)
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
self.__internalAddPackage(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
def __internalAddPackage(self, pp):
|
||||
""" Adds the indicated "pending package" to the appropriate
|
||||
list(s) for downloading and installing. Assumes packageLock
|
||||
is already held."""
|
||||
|
||||
if pp in self.packages:
|
||||
# Already added.
|
||||
return
|
||||
|
||||
self.packages.append(pp)
|
||||
|
||||
# We always add the package to needsDescFile, even if we
|
||||
# already have its desc file; this guarantees that packages
|
||||
# are downloaded in the order they are added.
|
||||
self.needsDescFile.append(pp)
|
||||
if not self.descFileTask:
|
||||
self.descFileTask = taskMgr.add(
|
||||
self.__getDescFileTask, 'getDescFile',
|
||||
taskChain = self.taskChain)
|
||||
|
||||
def donePackages(self):
|
||||
""" After calling addPackage() for each package to be
|
||||
installed, call donePackages() to mark the end of the list.
|
||||
This is necessary to determine what the complete set of
|
||||
packages is (and therefore how large the total download size
|
||||
is). None of the low-level callbacks will be made before this
|
||||
call. """
|
||||
|
||||
if self.state != self.S_initial:
|
||||
# We've already been here.
|
||||
return
|
||||
|
||||
# Throw the messages for packages that were already done
|
||||
# before we started.
|
||||
for pp in self.earlyDone:
|
||||
self.__donePackage(pp, True)
|
||||
self.earlyDone = []
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
if self.state != self.S_initial:
|
||||
return
|
||||
self.state = self.S_ready
|
||||
if not self.needsDescFile:
|
||||
# All package desc files are already available; so begin.
|
||||
self.__prepareToStart()
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if not self.packages:
|
||||
# Trivial no-op.
|
||||
self.__callDownloadFinished(True)
|
||||
|
||||
def downloadStarted(self):
|
||||
""" This callback is made at some point after donePackages()
|
||||
is called; at the time of this callback, the total download
|
||||
size is known, and we can sensibly report progress through the
|
||||
whole. """
|
||||
|
||||
self.notify.info("downloadStarted")
|
||||
|
||||
def packageStarted(self, package):
|
||||
""" This callback is made for each package between
|
||||
downloadStarted() and downloadFinished() to indicate the start
|
||||
of a new package. """
|
||||
|
||||
self.notify.debug("packageStarted: %s" % (package.packageName))
|
||||
|
||||
def packageProgress(self, package, progress):
|
||||
""" This callback is made repeatedly between packageStarted()
|
||||
and packageFinished() to update the current progress on the
|
||||
indicated package only. The progress value ranges from 0
|
||||
(beginning) to 1 (complete). """
|
||||
|
||||
self.notify.debug("packageProgress: %s %s" % (package.packageName, progress))
|
||||
|
||||
def downloadProgress(self, overallProgress):
|
||||
""" This callback is made repeatedly between downloadStarted()
|
||||
and downloadFinished() to update the current progress through
|
||||
all packages. The progress value ranges from 0 (beginning) to
|
||||
1 (complete). """
|
||||
|
||||
self.notify.debug("downloadProgress: %s" % (overallProgress))
|
||||
|
||||
def packageFinished(self, package, success):
|
||||
""" This callback is made for each package between
|
||||
downloadStarted() and downloadFinished() to indicate that a
|
||||
package has finished downloading. If success is true, there
|
||||
were no problems and the package is now installed.
|
||||
|
||||
If this package did not require downloading (because it was
|
||||
already downloaded), this callback will be made immediately,
|
||||
*without* a corresponding call to packageStarted(), and may
|
||||
even be made before downloadStarted(). """
|
||||
|
||||
self.notify.info("packageFinished: %s %s" % (package.packageName, success))
|
||||
|
||||
def downloadFinished(self, success):
|
||||
""" This callback is made when all of the packages have been
|
||||
downloaded and installed (or there has been some failure). If
|
||||
all packages where successfully installed, success is True.
|
||||
|
||||
If there were no packages that required downloading, this
|
||||
callback will be made immediately, *without* a corresponding
|
||||
call to downloadStarted(). """
|
||||
|
||||
self.notify.info("downloadFinished: %s" % (success))
|
||||
|
||||
def __prepareToStart(self):
|
||||
""" This is called internally when transitioning from S_ready
|
||||
to S_started. It sets up whatever initial values are
|
||||
needed. Assumes self.packageLock is held. Returns False if
|
||||
there were no packages to download, and the state was
|
||||
therefore transitioned immediately to S_done. """
|
||||
|
||||
if not self.needsDownload:
|
||||
self.state = self.S_done
|
||||
return False
|
||||
|
||||
self.state = self.S_started
|
||||
|
||||
assert not self.downloadTask
|
||||
self.downloadTask = taskMgr.add(
|
||||
self.__downloadPackageTask, 'downloadPackage',
|
||||
taskChain = self.taskChain)
|
||||
|
||||
assert not self.progressTask
|
||||
self.progressTask = taskMgr.add(
|
||||
self.__progressTask, 'packageProgress')
|
||||
|
||||
return True
|
||||
|
||||
def __allHaveDesc(self):
|
||||
""" This method is called internally when all of the pending
|
||||
packages have their desc info. """
|
||||
working = True
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
if self.state == self.S_ready:
|
||||
# We've already called donePackages(), so move on now.
|
||||
working = self.__prepareToStart()
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if not working:
|
||||
self.__callDownloadFinished(True)
|
||||
|
||||
def __packageStarted(self, pp):
|
||||
""" This method is called when a single package is beginning
|
||||
to download. """
|
||||
|
||||
self.__callDownloadStarted()
|
||||
self.__callPackageStarted(pp)
|
||||
|
||||
def __packageDone(self, pp):
|
||||
""" This method is called when a single package has been
|
||||
downloaded and installed, or has failed. """
|
||||
|
||||
self.__callPackageFinished(pp, pp.success)
|
||||
pp.notified = True
|
||||
|
||||
# See if there are more packages to go.
|
||||
success = True
|
||||
allDone = True
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
for pp in self.packages:
|
||||
if pp.notified:
|
||||
success = success and pp.success
|
||||
else:
|
||||
allDone = False
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
if allDone:
|
||||
self.__callDownloadFinished(success)
|
||||
|
||||
def __callPackageStarted(self, pp):
|
||||
""" Calls the packageStarted() callback for a particular
|
||||
package if it has not already been called, being careful to
|
||||
avoid race conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not pp.calledPackageStarted:
|
||||
self.packageStarted(pp.package)
|
||||
self.packageProgress(pp.package, 0)
|
||||
pp.calledPackageStarted = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __callPackageFinished(self, pp, success):
|
||||
""" Calls the packageFinished() callback for a paricular
|
||||
package if it has not already been called, being careful to
|
||||
avoid race conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not pp.calledPackageFinished:
|
||||
if success:
|
||||
self.packageProgress(pp.package, 1)
|
||||
self.packageFinished(pp.package, success)
|
||||
pp.calledPackageFinished = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __callDownloadStarted(self):
|
||||
""" Calls the downloadStarted() callback if it has not already
|
||||
been called, being careful to avoid race conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not self.calledDownloadStarted:
|
||||
self.downloadStarted()
|
||||
self.downloadProgress(0)
|
||||
self.calledDownloadStarted = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __callDownloadFinished(self, success):
|
||||
""" Calls the downloadFinished() callback if it has not
|
||||
already been called, being careful to avoid race
|
||||
conditions. """
|
||||
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not self.calledDownloadFinished:
|
||||
if success:
|
||||
self.downloadProgress(1)
|
||||
self.downloadFinished(success)
|
||||
self.calledDownloadFinished = True
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
def __getDescFileTask(self, task):
|
||||
|
||||
""" This task runs on the aysynchronous task chain; each pass,
|
||||
it extracts one package from self.needsDescFile and downloads
|
||||
its desc file. On success, it adds the package to
|
||||
self.needsDownload. """
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# If we've finished all of the packages that need desc
|
||||
# files, stop the task.
|
||||
if not self.needsDescFile:
|
||||
self.descFileTask = None
|
||||
|
||||
eventName = 'PackageInstaller-%s-allHaveDesc' % self.uniqueId
|
||||
messenger.send(eventName, taskChain = 'default')
|
||||
|
||||
return task.done
|
||||
pp = self.needsDescFile[0]
|
||||
del self.needsDescFile[0]
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
# Now serve this one package.
|
||||
if not pp.checkDescFile():
|
||||
if not pp.getDescFile(self.appRunner.http):
|
||||
self.__donePackage(pp, False)
|
||||
return task.cont
|
||||
|
||||
# This package is now ready to be downloaded. We always add
|
||||
# it to needsDownload, even if it's already downloaded, to
|
||||
# guarantee ordering of packages.
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# Also add any packages required by this one.
|
||||
for packageName, version, host in pp.package.requires:
|
||||
pp2 = self.PendingPackage(packageName, version, host)
|
||||
self.__internalAddPackage(pp2)
|
||||
self.needsDownload.append(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
return task.cont
|
||||
|
||||
def __downloadPackageTask(self, task):
|
||||
|
||||
""" This task runs on the aysynchronous task chain; each pass,
|
||||
it extracts one package from self.needsDownload and downloads
|
||||
it. """
|
||||
|
||||
while True:
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
# If we're done downloading, stop the task.
|
||||
if self.state == self.S_done or not self.needsDownload:
|
||||
self.downloadTask = None
|
||||
self.packageLock.release()
|
||||
yield task.done; return
|
||||
|
||||
assert self.state == self.S_started
|
||||
pp = self.needsDownload[0]
|
||||
del self.needsDownload[0]
|
||||
except:
|
||||
self.packageLock.release()
|
||||
raise
|
||||
self.packageLock.release()
|
||||
|
||||
# Now serve this one package.
|
||||
eventName = 'PackageInstaller-%s-packageStarted' % self.uniqueId
|
||||
messenger.send(eventName, [pp], taskChain = 'default')
|
||||
|
||||
if not pp.package.hasPackage:
|
||||
for token in pp.package.downloadPackageGenerator(self.appRunner.http):
|
||||
if token == pp.package.stepContinue:
|
||||
yield task.cont
|
||||
else:
|
||||
break
|
||||
|
||||
if token != pp.package.stepComplete:
|
||||
pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
|
||||
pc.start()
|
||||
self.__donePackage(pp, False)
|
||||
pc.stop()
|
||||
yield task.cont
|
||||
continue
|
||||
|
||||
# Successfully downloaded and installed.
|
||||
pc = PStatCollector(':App:PackageInstaller:donePackage:%s' % (pp.package.packageName))
|
||||
pc.start()
|
||||
self.__donePackage(pp, True)
|
||||
pc.stop()
|
||||
|
||||
# Continue the loop without yielding, so we pick up the
|
||||
# next package within this same frame.
|
||||
|
||||
def __donePackage(self, pp, success):
|
||||
""" Marks the indicated package as done, either successfully
|
||||
or otherwise. """
|
||||
assert not pp.done
|
||||
|
||||
if success:
|
||||
pc = PStatCollector(':App:PackageInstaller:install:%s' % (pp.package.packageName))
|
||||
pc.start()
|
||||
pp.package.installPackage(self.appRunner)
|
||||
pc.stop()
|
||||
|
||||
self.packageLock.acquire()
|
||||
try:
|
||||
pp.done = True
|
||||
pp.success = success
|
||||
if success:
|
||||
self.done.append(pp)
|
||||
else:
|
||||
self.failed.append(pp)
|
||||
finally:
|
||||
self.packageLock.release()
|
||||
|
||||
eventName = 'PackageInstaller-%s-packageDone' % self.uniqueId
|
||||
messenger.send(eventName, [pp], taskChain = 'default')
|
||||
|
||||
def __progressTask(self, task):
|
||||
self.callbackLock.acquire()
|
||||
try:
|
||||
if not self.calledDownloadStarted:
|
||||
# We haven't yet officially started the download.
|
||||
return task.cont
|
||||
|
||||
if self.calledDownloadFinished:
|
||||
# We've officially ended the download.
|
||||
self.progressTask = None
|
||||
return task.done
|
||||
|
||||
downloadEffort = 0
|
||||
currentDownloadSize = 0
|
||||
for pp in self.packages:
|
||||
downloadEffort += pp.downloadEffort + pp.prevDownloadedEffort
|
||||
packageProgress = pp.getProgress()
|
||||
currentDownloadSize += pp.downloadEffort * packageProgress + pp.prevDownloadedEffort
|
||||
if pp.calledPackageStarted and not pp.calledPackageFinished:
|
||||
self.packageProgress(pp.package, packageProgress)
|
||||
|
||||
if not downloadEffort:
|
||||
progress = 1
|
||||
else:
|
||||
progress = float(currentDownloadSize) / float(downloadEffort)
|
||||
self.downloadProgress(progress)
|
||||
|
||||
finally:
|
||||
self.callbackLock.release()
|
||||
|
||||
return task.cont
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
__all__ = ["PackageMerger", "PackageMergerError"]
|
||||
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from direct.p3d.SeqValue import SeqValue
|
||||
from direct.directnotify.DirectNotifyGlobal import *
|
||||
from panda3d.core import *
|
||||
import shutil
|
||||
import os
|
||||
|
||||
class PackageMergerError(Exception):
|
||||
pass
|
||||
|
||||
class PackageMerger:
|
||||
""" This class will combine two or more separately-built stage
|
||||
directories, the output of Packager.py or the ppackage tool, into
|
||||
a single output directory. It assumes that the clocks on all
|
||||
hosts are in sync, so that the file across all builds with the
|
||||
most recent timestamp (indicated in the contents.xml file) is
|
||||
always the most current version of the file. """
|
||||
|
||||
notify = directNotify.newCategory("PackageMerger")
|
||||
|
||||
class PackageEntry:
|
||||
""" This corresponds to a <package> entry in the contents.xml
|
||||
file. """
|
||||
|
||||
def __init__(self, xpackage, sourceDir):
|
||||
self.sourceDir = sourceDir
|
||||
self.loadXml(xpackage)
|
||||
|
||||
def getKey(self):
|
||||
""" Returns a tuple used for sorting the PackageEntry
|
||||
objects uniquely per package. """
|
||||
return (self.packageName, self.platform, self.version)
|
||||
|
||||
def isNewer(self, other):
|
||||
return self.descFile.timestamp > other.descFile.timestamp
|
||||
|
||||
def loadXml(self, xpackage):
|
||||
self.packageName = xpackage.Attribute('name')
|
||||
self.platform = xpackage.Attribute('platform')
|
||||
self.version = xpackage.Attribute('version')
|
||||
solo = xpackage.Attribute('solo')
|
||||
self.solo = int(solo or '0')
|
||||
perPlatform = xpackage.Attribute('per_platform')
|
||||
self.perPlatform = int(perPlatform or '0')
|
||||
|
||||
self.descFile = FileSpec()
|
||||
self.descFile.loadXml(xpackage)
|
||||
|
||||
self.validatePackageContents()
|
||||
|
||||
self.descFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
|
||||
|
||||
self.packageSeq = SeqValue()
|
||||
self.packageSeq.loadXml(xpackage, 'seq')
|
||||
self.packageSetVer = SeqValue()
|
||||
self.packageSetVer.loadXml(xpackage, 'set_ver')
|
||||
|
||||
self.importDescFile = None
|
||||
ximport = xpackage.FirstChildElement('import')
|
||||
if ximport:
|
||||
self.importDescFile = FileSpec()
|
||||
self.importDescFile.loadXml(ximport)
|
||||
self.importDescFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
|
||||
|
||||
def makeXml(self):
|
||||
""" Returns a new TiXmlElement. """
|
||||
xpackage = TiXmlElement('package')
|
||||
xpackage.SetAttribute('name', self.packageName)
|
||||
if self.platform:
|
||||
xpackage.SetAttribute('platform', self.platform)
|
||||
if self.version:
|
||||
xpackage.SetAttribute('version', self.version)
|
||||
if self.solo:
|
||||
xpackage.SetAttribute('solo', '1')
|
||||
if self.perPlatform:
|
||||
xpackage.SetAttribute('per_platform', '1')
|
||||
|
||||
self.descFile.storeXml(xpackage)
|
||||
self.packageSeq.storeXml(xpackage, 'seq')
|
||||
self.packageSetVer.storeXml(xpackage, 'set_ver')
|
||||
|
||||
if self.importDescFile:
|
||||
ximport = TiXmlElement('import')
|
||||
self.importDescFile.storeXml(ximport)
|
||||
xpackage.InsertEndChild(ximport)
|
||||
|
||||
return xpackage
|
||||
|
||||
def validatePackageContents(self):
|
||||
""" Validates the contents of the package directory itself
|
||||
against the expected hashes and timestamps. Updates
|
||||
hashes and timestamps where needed. """
|
||||
|
||||
if self.solo:
|
||||
return
|
||||
|
||||
needsChange = False
|
||||
packageDescFullpath = Filename(self.sourceDir, self.descFile.filename)
|
||||
packageDir = Filename(packageDescFullpath.getDirname())
|
||||
doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
message = "Could not read XML file: %s" % (self.descFile.filename)
|
||||
raise OSError(message)
|
||||
|
||||
xpackage = doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
message = "No package definition: %s" % (self.descFile.filename)
|
||||
raise OSError(message)
|
||||
|
||||
xcompressed = xpackage.FirstChildElement('compressed_archive')
|
||||
if xcompressed:
|
||||
spec = FileSpec()
|
||||
spec.loadXml(xcompressed)
|
||||
if not spec.quickVerify(packageDir = packageDir, notify = PackageMerger.notify, correctSelf = True):
|
||||
spec.storeXml(xcompressed)
|
||||
needsChange = True
|
||||
|
||||
xpatch = xpackage.FirstChildElement('patch')
|
||||
while xpatch:
|
||||
spec = FileSpec()
|
||||
spec.loadXml(xpatch)
|
||||
if not spec.quickVerify(packageDir = packageDir, notify = PackageMerger.notify, correctSelf = True):
|
||||
spec.storeXml(xpatch)
|
||||
needsChange = True
|
||||
|
||||
xpatch = xpatch.NextSiblingElement('patch')
|
||||
|
||||
if needsChange:
|
||||
PackageMerger.notify.info("Rewriting %s" % (self.descFile.filename))
|
||||
doc.SaveFile()
|
||||
self.descFile.quickVerify(packageDir = self.sourceDir, notify = PackageMerger.notify, correctSelf = True)
|
||||
|
||||
# PackageMerger constructor
|
||||
def __init__(self, installDir):
|
||||
self.installDir = installDir
|
||||
self.xhost = None
|
||||
self.contents = {}
|
||||
self.maxAge = None
|
||||
self.contentsSeq = SeqValue()
|
||||
|
||||
# We allow the first one to fail quietly.
|
||||
self.__readContentsFile(self.installDir, None)
|
||||
|
||||
def __readContentsFile(self, sourceDir, packageNames):
|
||||
""" Reads the contents.xml file from the indicated sourceDir,
|
||||
and updates the internal set of packages appropriately. """
|
||||
|
||||
assert sourceDir != None, "No source directory was specified!"
|
||||
contentsFilename = Filename(sourceDir, 'contents.xml')
|
||||
doc = TiXmlDocument(contentsFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
# Couldn't read file.
|
||||
return False
|
||||
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if xcontents:
|
||||
maxAge = xcontents.Attribute('max_age')
|
||||
if maxAge:
|
||||
maxAge = int(maxAge)
|
||||
if self.maxAge is None:
|
||||
self.maxAge = maxAge
|
||||
else:
|
||||
self.maxAge = min(self.maxAge, maxAge)
|
||||
|
||||
contentsSeq = SeqValue()
|
||||
if contentsSeq.loadXml(xcontents):
|
||||
self.contentsSeq = max(self.contentsSeq, contentsSeq)
|
||||
|
||||
xhost = xcontents.FirstChildElement('host')
|
||||
if xhost:
|
||||
self.xhost = xhost.Clone()
|
||||
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
pe = self.PackageEntry(xpackage, sourceDir)
|
||||
|
||||
# Filter out any packages not listed in
|
||||
# packageNames (unless packageNames is None,
|
||||
# in which case don't filter anything).
|
||||
if packageNames is None or pe.packageName in packageNames:
|
||||
other = self.contents.get(pe.getKey(), None)
|
||||
if not other or pe.isNewer(other):
|
||||
# Store this package in the resulting output.
|
||||
self.contents[pe.getKey()] = pe
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
self.contentsDoc = doc
|
||||
|
||||
return True
|
||||
|
||||
def __writeContentsFile(self):
|
||||
""" Writes the contents.xml file at the end of processing. """
|
||||
|
||||
filename = Filename(self.installDir, 'contents.xml')
|
||||
doc = TiXmlDocument(filename.toOsSpecific())
|
||||
decl = TiXmlDeclaration("1.0", "utf-8", "")
|
||||
doc.InsertEndChild(decl)
|
||||
|
||||
xcontents = TiXmlElement('contents')
|
||||
if self.xhost:
|
||||
xcontents.InsertEndChild(self.xhost)
|
||||
|
||||
if self.maxAge is not None:
|
||||
xcontents.SetAttribute('max_age', str(self.maxAge))
|
||||
self.contentsSeq.storeXml(xcontents)
|
||||
|
||||
contents = list(self.contents.items())
|
||||
contents.sort()
|
||||
for key, pe in contents:
|
||||
xpackage = pe.makeXml()
|
||||
xcontents.InsertEndChild(xpackage)
|
||||
|
||||
doc.InsertEndChild(xcontents)
|
||||
doc.SaveFile()
|
||||
|
||||
def __copySubdirectory(self, pe):
|
||||
""" Copies the subdirectory referenced in the indicated
|
||||
PackageEntry object into the installDir, replacing the
|
||||
contents of any similarly-named subdirectory already
|
||||
there. """
|
||||
|
||||
dirname = Filename(pe.descFile.filename).getDirname()
|
||||
self.notify.info("copying %s" % (dirname))
|
||||
sourceDirname = Filename(pe.sourceDir, dirname)
|
||||
targetDirname = Filename(self.installDir, dirname)
|
||||
|
||||
self.__rCopyTree(sourceDirname, targetDirname)
|
||||
|
||||
def __rCopyTree(self, sourceFilename, targetFilename):
|
||||
""" Recursively copies the contents of sourceDirname onto
|
||||
targetDirname. This behaves like shutil.copytree, but it does
|
||||
not remove pre-existing subdirectories. """
|
||||
|
||||
if targetFilename.exists():
|
||||
if not targetFilename.isDirectory():
|
||||
# Delete any regular files in the way.
|
||||
targetFilename.unlink()
|
||||
|
||||
elif not sourceFilename.isDirectory():
|
||||
# If the source file is a regular file, but the target
|
||||
# file is a directory, completely remove the target
|
||||
# file.
|
||||
shutil.rmtree(targetFilename.toOsSpecific())
|
||||
|
||||
else:
|
||||
# Both the source file and target file are
|
||||
# directories.
|
||||
|
||||
# We have to clean out the target directory first.
|
||||
# Instead of using shutil.rmtree(), remove the files in
|
||||
# this directory one at a time, so we don't inadvertently
|
||||
# clean out subdirectories too.
|
||||
files = os.listdir(targetFilename.toOsSpecific())
|
||||
for file in files:
|
||||
f = Filename(targetFilename, file)
|
||||
if f.isRegularFile():
|
||||
f.unlink()
|
||||
|
||||
if sourceFilename.isDirectory():
|
||||
# Recursively copying a directory.
|
||||
Filename(targetFilename, '').makeDir()
|
||||
files = os.listdir(sourceFilename.toOsSpecific())
|
||||
for file in files:
|
||||
self.__rCopyTree(Filename(sourceFilename, file),
|
||||
Filename(targetFilename, file))
|
||||
else:
|
||||
# Copying a regular file.
|
||||
sourceFilename.copyTo(targetFilename)
|
||||
|
||||
# Also try to copy the timestamp, but don't fuss too much
|
||||
# if it doesn't work.
|
||||
try:
|
||||
st = os.stat(sourceFilename.toOsSpecific())
|
||||
os.utime(targetFilename.toOsSpecific(), (st.st_atime, st.st_mtime))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def merge(self, sourceDir, packageNames = None):
|
||||
""" Adds the contents of the indicated source directory into
|
||||
the current pool. If packageNames is not None, it is a list
|
||||
of package names that we wish to include from the source;
|
||||
packages not named in this list will be unchanged. """
|
||||
|
||||
if not self.__readContentsFile(sourceDir, packageNames):
|
||||
message = "Couldn't read %s" % (sourceDir)
|
||||
raise PackageMergerError(message)
|
||||
|
||||
def close(self):
|
||||
""" Finalizes the results of all of the previous calls to
|
||||
merge(), writes the new contents.xml file, and copies in all
|
||||
of the new contents. """
|
||||
|
||||
dirname = Filename(self.installDir, '')
|
||||
dirname.makeDir()
|
||||
|
||||
for pe in self.contents.values():
|
||||
if pe.sourceDir != self.installDir:
|
||||
# Here's a new subdirectory we have to copy in.
|
||||
self.__copySubdirectory(pe)
|
||||
|
||||
self.contentsSeq += 1
|
||||
self.__writeContentsFile()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,814 +0,0 @@
|
||||
__all__ = ["PatchMaker"]
|
||||
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from direct.p3d.SeqValue import SeqValue
|
||||
from panda3d.core import *
|
||||
import copy
|
||||
|
||||
class PatchMaker:
|
||||
""" This class will operate on an existing package install
|
||||
directory, as generated by the Packager, and create patchfiles
|
||||
between versions as needed. It is also used at runtime, to apply
|
||||
the downloaded patches. """
|
||||
|
||||
class PackageVersion:
|
||||
""" A specific patch version of a package. This is not just
|
||||
the package's "version" string; it also corresponds to the
|
||||
particular patch version, which increments independently of
|
||||
the "version". """
|
||||
|
||||
def __init__(self, packageName, platform, version, hostUrl, file):
|
||||
self.packageName = packageName
|
||||
self.platform = platform
|
||||
self.version = version
|
||||
self.hostUrl = hostUrl
|
||||
self.file = file
|
||||
self.printName = None
|
||||
|
||||
# The Package object that produces this version, if this
|
||||
# is the current, base, or top form, respectively.
|
||||
self.packageCurrent = None
|
||||
self.packageBase = None
|
||||
self.packageTop = None
|
||||
|
||||
# A list of patchfiles that can produce this version.
|
||||
self.fromPatches = []
|
||||
|
||||
# A list of patchfiles that can start from this version.
|
||||
self.toPatches = []
|
||||
|
||||
# A temporary file for re-creating the archive file for
|
||||
# this version.
|
||||
self.tempFile = None
|
||||
|
||||
def cleanup(self):
|
||||
if self.tempFile:
|
||||
self.tempFile.unlink()
|
||||
|
||||
def getPatchChain(self, startPv, alreadyVisited = []):
|
||||
""" Returns a list of patches that, when applied in
|
||||
sequence to the indicated PackageVersion object, will
|
||||
produce this PackageVersion object. Returns None if no
|
||||
chain can be found. """
|
||||
|
||||
if self is startPv:
|
||||
# We're already here. A zero-length patch chain is
|
||||
# therefore the answer.
|
||||
return []
|
||||
if self in alreadyVisited:
|
||||
# We've already been here; this is a loop. Avoid
|
||||
# infinite recursion.
|
||||
return None
|
||||
|
||||
alreadyVisited = alreadyVisited[:]
|
||||
alreadyVisited.append(self)
|
||||
|
||||
bestPatchChain = None
|
||||
for patchfile in self.fromPatches:
|
||||
fromPv = patchfile.fromPv
|
||||
patchChain = fromPv.getPatchChain(startPv, alreadyVisited)
|
||||
if patchChain is not None:
|
||||
# There's a path through this patchfile.
|
||||
patchChain = patchChain + [patchfile]
|
||||
if bestPatchChain is None or len(patchChain) < len(bestPatchChain):
|
||||
bestPatchChain = patchChain
|
||||
|
||||
# Return the shortest path found, or None if there were no
|
||||
# paths found.
|
||||
return bestPatchChain
|
||||
|
||||
def getRecreateFilePlan(self, alreadyVisited = []):
|
||||
""" Returns the tuple (startFile, startPv, plan),
|
||||
describing how to recreate the archive file for this
|
||||
version. startFile and startPv is the Filename and
|
||||
packageVersion of the file to start with, and plan is a
|
||||
list of tuples (patchfile, pv), listing the patches to
|
||||
apply in sequence, and the packageVersion object
|
||||
associated with each patch. Returns (None, None, None) if
|
||||
there is no way to recreate this archive file. """
|
||||
|
||||
if self.tempFile:
|
||||
return (self.tempFile, self, [])
|
||||
|
||||
if self in alreadyVisited:
|
||||
# We've already been here; this is a loop. Avoid
|
||||
# infinite recursion.
|
||||
return (None, None, None)
|
||||
|
||||
alreadyVisited = alreadyVisited[:]
|
||||
alreadyVisited.append(self)
|
||||
|
||||
if self.packageCurrent:
|
||||
# This PackageVersion instance represents the current
|
||||
# version of some package.
|
||||
package = self.packageCurrent
|
||||
return (Filename(package.packageDir, package.compressedFilename), self, [])
|
||||
|
||||
if self.packageBase:
|
||||
# This PackageVersion instance represents the base
|
||||
# (oldest) version of some package.
|
||||
package = self.packageBase
|
||||
return (Filename(package.packageDir, package.baseFile.filename + '.pz'), self, [])
|
||||
|
||||
# We'll need to re-create the file.
|
||||
bestPlan = None
|
||||
bestStartFile = None
|
||||
bestStartPv = None
|
||||
for patchfile in self.fromPatches:
|
||||
fromPv = patchfile.fromPv
|
||||
startFile, startPv, plan = fromPv.getRecreateFilePlan(alreadyVisited)
|
||||
if plan is not None:
|
||||
# There's a path through this patchfile.
|
||||
plan = plan + [(patchfile, self)]
|
||||
if bestPlan is None or len(plan) < len(bestPlan):
|
||||
bestPlan = plan
|
||||
bestStartFile = startFile
|
||||
bestStartPv = startPv
|
||||
|
||||
# Return the shortest path found, or None if there were no
|
||||
# paths found.
|
||||
return (bestStartFile, bestStartPv, bestPlan)
|
||||
|
||||
def getFile(self):
|
||||
""" Returns the Filename of the archive file associated
|
||||
with this version. If the file doesn't actually exist on
|
||||
disk, a temporary file will be created. Returns None if
|
||||
the file can't be recreated. """
|
||||
|
||||
startFile, startPv, plan = self.getRecreateFilePlan()
|
||||
|
||||
if startFile.getExtension() in ('pz', 'gz'):
|
||||
# If the starting file is compressed, we have to
|
||||
# decompress it first.
|
||||
assert startPv.tempFile is None
|
||||
startPv.tempFile = Filename.temporary('', 'patch_')
|
||||
if not decompressFile(startFile, startPv.tempFile):
|
||||
# Failure trying to decompress the file.
|
||||
return None
|
||||
startFile = startPv.tempFile
|
||||
|
||||
if not plan:
|
||||
# If plan is a zero-length list, we're already
|
||||
# here--return startFile. If plan is None, there's no
|
||||
# solution, and startFile is None. In either case, we
|
||||
# can return startFile.
|
||||
return startFile
|
||||
|
||||
# If plan is a non-empty list, we have to walk the list to
|
||||
# apply the patch plan.
|
||||
prevFile = startFile
|
||||
for patchfile, pv in plan:
|
||||
fromPv = patchfile.fromPv
|
||||
patchFilename = Filename(patchfile.package.packageDir, patchfile.file.filename)
|
||||
result = self.applyPatch(prevFile, patchFilename)
|
||||
if not result:
|
||||
# Failure trying to re-create the file.
|
||||
return None
|
||||
|
||||
pv.tempFile = result
|
||||
prevFile = result
|
||||
|
||||
# Successfully patched.
|
||||
assert pv is self and prevFile is self.tempFile
|
||||
return prevFile
|
||||
|
||||
def applyPatch(self, origFile, patchFilename):
|
||||
""" Applies the named patch to the indicated original
|
||||
file, storing the results in a temporary file, and returns
|
||||
that temporary Filename. Returns None on failure. """
|
||||
|
||||
result = Filename.temporary('', 'patch_')
|
||||
p = Patchfile()
|
||||
if not p.apply(patchFilename, origFile, result):
|
||||
print("Internal patching failed: %s" % (patchFilename))
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
def getNext(self, package):
|
||||
""" Gets the next patch in the chain towards this
|
||||
package. """
|
||||
for patch in self.toPatches:
|
||||
if patch.packageName == package.packageName and \
|
||||
patch.platform == package.platform and \
|
||||
patch.version == package.version and \
|
||||
patch.hostUrl == package.hostUrl:
|
||||
return patch.toPv
|
||||
|
||||
return None
|
||||
|
||||
class Patchfile:
|
||||
""" A single patchfile for a package. """
|
||||
|
||||
def __init__(self, package):
|
||||
self.package = package
|
||||
self.packageName = package.packageName
|
||||
self.platform = package.platform
|
||||
self.version = package.version
|
||||
self.hostUrl = None
|
||||
|
||||
# FileSpec for the patchfile itself
|
||||
self.file = None
|
||||
|
||||
# FileSpec for the package file that the patch is applied to
|
||||
self.sourceFile = None
|
||||
|
||||
# FileSpec for the package file that the patch generates
|
||||
self.targetFile = None
|
||||
|
||||
# The PackageVersion corresponding to our sourceFile
|
||||
self.fromPv = None
|
||||
|
||||
# The PackageVersion corresponding to our targetFile
|
||||
self.toPv = None
|
||||
|
||||
def getSourceKey(self):
|
||||
""" Returns the key for locating the package that this
|
||||
patchfile can be applied to. """
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.sourceFile)
|
||||
|
||||
def getTargetKey(self):
|
||||
""" Returns the key for locating the package that this
|
||||
patchfile will generate. """
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.targetFile)
|
||||
|
||||
def fromFile(self, packageDir, patchFilename, sourceFile, targetFile):
|
||||
""" Creates the data structures from an existing patchfile
|
||||
on disk. """
|
||||
|
||||
self.file = FileSpec()
|
||||
self.file.fromFile(packageDir, patchFilename)
|
||||
self.sourceFile = sourceFile
|
||||
self.targetFile = targetFile
|
||||
|
||||
def loadXml(self, xpatch):
|
||||
""" Reads the data structures from an xml file. """
|
||||
|
||||
self.packageName = xpatch.Attribute('name') or self.packageName
|
||||
self.platform = xpatch.Attribute('platform') or self.platform
|
||||
self.version = xpatch.Attribute('version') or self.version
|
||||
self.hostUrl = xpatch.Attribute('host') or self.hostUrl
|
||||
|
||||
self.file = FileSpec()
|
||||
self.file.loadXml(xpatch)
|
||||
|
||||
xsource = xpatch.FirstChildElement('source')
|
||||
if xsource:
|
||||
self.sourceFile = FileSpec()
|
||||
self.sourceFile.loadXml(xsource)
|
||||
|
||||
xtarget = xpatch.FirstChildElement('target')
|
||||
if xtarget:
|
||||
self.targetFile = FileSpec()
|
||||
self.targetFile.loadXml(xtarget)
|
||||
|
||||
def makeXml(self, package):
|
||||
xpatch = TiXmlElement('patch')
|
||||
|
||||
if self.packageName != package.packageName:
|
||||
xpatch.SetAttribute('name', self.packageName)
|
||||
if self.platform != package.platform:
|
||||
xpatch.SetAttribute('platform', self.platform)
|
||||
if self.version != package.version:
|
||||
xpatch.SetAttribute('version', self.version)
|
||||
if self.hostUrl != package.hostUrl:
|
||||
xpatch.SetAttribute('host', self.hostUrl)
|
||||
|
||||
self.file.storeXml(xpatch)
|
||||
|
||||
xsource = TiXmlElement('source')
|
||||
self.sourceFile.storeMiniXml(xsource)
|
||||
xpatch.InsertEndChild(xsource)
|
||||
|
||||
xtarget = TiXmlElement('target')
|
||||
self.targetFile.storeMiniXml(xtarget)
|
||||
xpatch.InsertEndChild(xtarget)
|
||||
|
||||
return xpatch
|
||||
|
||||
class Package:
|
||||
""" This is a particular package. This contains all of the
|
||||
information needed to reconstruct the package's desc file. """
|
||||
|
||||
def __init__(self, packageDesc, patchMaker, xpackage = None):
|
||||
self.packageDir = Filename(patchMaker.installDir, packageDesc.getDirname())
|
||||
self.packageDesc = packageDesc
|
||||
self.patchMaker = patchMaker
|
||||
self.contentsDocPackage = xpackage
|
||||
self.patchVersion = 1
|
||||
self.currentPv = None
|
||||
self.basePv = None
|
||||
self.topPv = None
|
||||
|
||||
self.packageName = None
|
||||
self.platform = None
|
||||
self.version = None
|
||||
self.hostUrl = None
|
||||
self.currentFile = None
|
||||
self.baseFile = None
|
||||
|
||||
self.doc = None
|
||||
self.anyChanges = False
|
||||
self.patches = []
|
||||
|
||||
def getCurrentKey(self):
|
||||
""" Returns the key to locate the current version of this
|
||||
package. """
|
||||
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.currentFile)
|
||||
|
||||
def getBaseKey(self):
|
||||
""" Returns the key to locate the "base" or oldest version
|
||||
of this package. """
|
||||
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.baseFile)
|
||||
|
||||
def getTopKey(self):
|
||||
""" Returns the key to locate the "top" or newest version
|
||||
of this package. """
|
||||
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, self.topFile)
|
||||
|
||||
def getGenericKey(self, fileSpec):
|
||||
""" Returns the key that has the indicated hash. """
|
||||
return (self.packageName, self.platform, self.version, self.hostUrl, fileSpec)
|
||||
|
||||
def readDescFile(self, doProcessing = False):
|
||||
""" Reads the existing package.xml file and stores it in
|
||||
this class for later rewriting. if doProcessing is true,
|
||||
it may massage the file and the directory contents in
|
||||
preparation for building patches. Returns true on
|
||||
success, false on failure. """
|
||||
|
||||
self.anyChanges = False
|
||||
|
||||
packageDescFullpath = Filename(self.patchMaker.installDir, self.packageDesc)
|
||||
self.doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
|
||||
if not self.doc.LoadFile():
|
||||
print("Couldn't read %s" % (packageDescFullpath))
|
||||
return False
|
||||
|
||||
xpackage = self.doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
return False
|
||||
self.packageName = xpackage.Attribute('name')
|
||||
self.platform = xpackage.Attribute('platform')
|
||||
self.version = xpackage.Attribute('version')
|
||||
|
||||
# All packages we defined in-line are assigned to the
|
||||
# "none" host. TODO: support patching from packages on
|
||||
# other hosts, which means we'll need to fill in a value
|
||||
# here for those hosts.
|
||||
self.hostUrl = None
|
||||
|
||||
self.currentFile = None
|
||||
self.baseFile = None
|
||||
self.topFile = None
|
||||
self.compressedFilename = None
|
||||
compressedFile = None
|
||||
|
||||
# Assume there are changes for this version, until we
|
||||
# discover that there aren't.
|
||||
isNewVersion = True
|
||||
|
||||
# Get the actual current version.
|
||||
xarchive = xpackage.FirstChildElement('uncompressed_archive')
|
||||
if xarchive:
|
||||
self.currentFile = FileSpec()
|
||||
self.currentFile.loadXml(xarchive)
|
||||
|
||||
# Get the top_version--the top (newest) of the patch
|
||||
# chain.
|
||||
xarchive = xpackage.FirstChildElement('top_version')
|
||||
if xarchive:
|
||||
self.topFile = FileSpec()
|
||||
self.topFile.loadXml(xarchive)
|
||||
|
||||
if self.topFile.hash == self.currentFile.hash:
|
||||
# No new version this pass.
|
||||
isNewVersion = False
|
||||
else:
|
||||
# There's a new version this pass. Update it.
|
||||
self.anyChanges = True
|
||||
|
||||
else:
|
||||
# If there isn't a top_version yet, we have to make
|
||||
# one, by duplicating the currentFile.
|
||||
self.topFile = copy.copy(self.currentFile)
|
||||
self.anyChanges = True
|
||||
|
||||
# Get the current patch version. If we have a
|
||||
# patch_version attribute, it refers to this particular
|
||||
# instance of the file, and that is the current patch
|
||||
# version number. If we only have a last_patch_version
|
||||
# attribute, it means a patch has not yet been built for
|
||||
# this particular instance, and that number is the
|
||||
# previous version's patch version number.
|
||||
patchVersion = xpackage.Attribute('patch_version')
|
||||
if patchVersion:
|
||||
self.patchVersion = int(patchVersion)
|
||||
else:
|
||||
patchVersion = xpackage.Attribute('last_patch_version')
|
||||
if patchVersion:
|
||||
self.patchVersion = int(patchVersion)
|
||||
if isNewVersion:
|
||||
self.patchVersion += 1
|
||||
self.anyChanges = True
|
||||
|
||||
# Put the patchVersion in the compressed filename, for
|
||||
# cache-busting. This means when the version changes, its
|
||||
# URL will also change, guaranteeing that users will
|
||||
# download the latest version, and not some stale cache
|
||||
# file.
|
||||
xcompressed = xpackage.FirstChildElement('compressed_archive')
|
||||
if xcompressed:
|
||||
compressedFile = FileSpec()
|
||||
compressedFile.loadXml(xcompressed)
|
||||
|
||||
oldCompressedFilename = compressedFile.filename
|
||||
self.compressedFilename = oldCompressedFilename
|
||||
|
||||
if doProcessing:
|
||||
newCompressedFilename = '%s.%s.pz' % (self.currentFile.filename, self.patchVersion)
|
||||
if newCompressedFilename != oldCompressedFilename:
|
||||
oldCompressedPathname = Filename(self.packageDir, oldCompressedFilename)
|
||||
newCompressedPathname = Filename(self.packageDir, newCompressedFilename)
|
||||
if oldCompressedPathname.renameTo(newCompressedPathname):
|
||||
compressedFile.fromFile(self.packageDir, newCompressedFilename)
|
||||
compressedFile.storeXml(xcompressed)
|
||||
|
||||
self.compressedFilename = newCompressedFilename
|
||||
self.anyChanges = True
|
||||
|
||||
# Get the base_version--the bottom (oldest) of the patch
|
||||
# chain.
|
||||
xarchive = xpackage.FirstChildElement('base_version')
|
||||
if xarchive:
|
||||
self.baseFile = FileSpec()
|
||||
self.baseFile.loadXml(xarchive)
|
||||
else:
|
||||
# If there isn't a base_version yet, we have to make
|
||||
# one, by duplicating the currentFile.
|
||||
self.baseFile = copy.copy(self.currentFile)
|
||||
|
||||
# Note that the we only store the compressed version
|
||||
# of base_filename on disk, but we store the md5 of
|
||||
# the uncompressed version in the xml file. To
|
||||
# emphasize this, we name it without the .pz extension
|
||||
# in the xml file, even though the compressed file on
|
||||
# disk actually has a .pz extension.
|
||||
self.baseFile.filename += '.base'
|
||||
|
||||
# Also duplicate the (compressed) file itself.
|
||||
if doProcessing and self.compressedFilename:
|
||||
fromPathname = Filename(self.packageDir, self.compressedFilename)
|
||||
toPathname = Filename(self.packageDir, self.baseFile.filename + '.pz')
|
||||
fromPathname.copyTo(toPathname)
|
||||
self.anyChanges = True
|
||||
|
||||
self.patches = []
|
||||
xpatch = xpackage.FirstChildElement('patch')
|
||||
while xpatch:
|
||||
patchfile = PatchMaker.Patchfile(self)
|
||||
patchfile.loadXml(xpatch)
|
||||
self.patches.append(patchfile)
|
||||
xpatch = xpatch.NextSiblingElement('patch')
|
||||
|
||||
return True
|
||||
|
||||
def writeDescFile(self):
|
||||
""" Rewrites the desc file with the new patch
|
||||
information. """
|
||||
|
||||
if not self.anyChanges:
|
||||
# No need to rewrite.
|
||||
return
|
||||
|
||||
xpackage = self.doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
return
|
||||
|
||||
packageSeq = SeqValue()
|
||||
packageSeq.loadXml(xpackage, 'seq')
|
||||
packageSeq += 1
|
||||
packageSeq.storeXml(xpackage, 'seq')
|
||||
|
||||
# Remove all of the old patch entries from the desc file
|
||||
# we read earlier.
|
||||
xremove = []
|
||||
for value in ['base_version', 'top_version', 'patch']:
|
||||
xpatch = xpackage.FirstChildElement(value)
|
||||
while xpatch:
|
||||
xremove.append(xpatch)
|
||||
xpatch = xpatch.NextSiblingElement(value)
|
||||
|
||||
for xelement in xremove:
|
||||
xpackage.RemoveChild(xelement)
|
||||
|
||||
xpackage.RemoveAttribute('last_patch_version')
|
||||
|
||||
# Now replace them with the current patch information.
|
||||
xpackage.SetAttribute('patch_version', str(self.patchVersion))
|
||||
|
||||
xarchive = TiXmlElement('base_version')
|
||||
self.baseFile.storeXml(xarchive)
|
||||
xpackage.InsertEndChild(xarchive)
|
||||
|
||||
# The current version is now the top version.
|
||||
xarchive = TiXmlElement('top_version')
|
||||
self.currentFile.storeXml(xarchive)
|
||||
xpackage.InsertEndChild(xarchive)
|
||||
|
||||
for patchfile in self.patches:
|
||||
xpatch = patchfile.makeXml(self)
|
||||
xpackage.InsertEndChild(xpatch)
|
||||
|
||||
self.doc.SaveFile()
|
||||
|
||||
# Also copy the seq to the import desc file, for
|
||||
# documentation purposes.
|
||||
|
||||
importDescFilename = str(self.packageDesc)[:-3] + 'import.xml'
|
||||
importDescFullpath = Filename(self.patchMaker.installDir, importDescFilename)
|
||||
doc = TiXmlDocument(importDescFullpath.toOsSpecific())
|
||||
if doc.LoadFile():
|
||||
xpackage = doc.FirstChildElement('package')
|
||||
if xpackage:
|
||||
packageSeq.storeXml(xpackage, 'seq')
|
||||
doc.SaveFile()
|
||||
else:
|
||||
print("Couldn't read %s" % (importDescFullpath))
|
||||
|
||||
if self.contentsDocPackage:
|
||||
# Now that we've rewritten the xml file, we have to
|
||||
# change the contents.xml file that references it to
|
||||
# indicate the new file hash.
|
||||
fileSpec = FileSpec()
|
||||
fileSpec.fromFile(self.patchMaker.installDir, self.packageDesc)
|
||||
fileSpec.storeXml(self.contentsDocPackage)
|
||||
|
||||
# Also important to update the import.xml hash.
|
||||
ximport = self.contentsDocPackage.FirstChildElement('import')
|
||||
if ximport:
|
||||
fileSpec = FileSpec()
|
||||
fileSpec.fromFile(self.patchMaker.installDir, importDescFilename)
|
||||
fileSpec.storeXml(ximport)
|
||||
|
||||
# Also copy the package seq value into the
|
||||
# contents.xml file, mainly for documentation purposes
|
||||
# (the authoritative seq value is within the desc
|
||||
# file).
|
||||
packageSeq.storeXml(self.contentsDocPackage, 'seq')
|
||||
|
||||
|
||||
# PatchMaker constructor.
|
||||
def __init__(self, installDir):
|
||||
self.installDir = installDir
|
||||
self.packageVersions = {}
|
||||
self.packages = []
|
||||
|
||||
def buildPatches(self, packageNames = None):
|
||||
""" Makes the patches required in a particular directory
|
||||
structure on disk. If packageNames is None, this makes
|
||||
patches for all packages; otherwise, it should be a list of
|
||||
package name strings, limiting the set of packages that are
|
||||
processed. """
|
||||
|
||||
if not self.readContentsFile():
|
||||
return False
|
||||
self.buildPatchChains()
|
||||
if packageNames is None:
|
||||
self.processAllPackages()
|
||||
else:
|
||||
self.processSomePackages(packageNames)
|
||||
|
||||
self.writeContentsFile()
|
||||
self.cleanup()
|
||||
return True
|
||||
|
||||
def cleanup(self):
|
||||
""" Should be called on exit to remove temporary files and
|
||||
such created during processing. """
|
||||
|
||||
for pv in self.packageVersions.values():
|
||||
pv.cleanup()
|
||||
|
||||
def getPatchChainToCurrent(self, descFilename, fileSpec):
|
||||
""" Reads the package defined in the indicated desc file, and
|
||||
constructs a patch chain from the version represented by
|
||||
fileSpec to the current version of this package, if possible.
|
||||
Returns the patch chain if successful, or None otherwise. """
|
||||
|
||||
package = self.readPackageDescFile(descFilename)
|
||||
if not package:
|
||||
return None
|
||||
|
||||
self.buildPatchChains()
|
||||
fromPv = self.getPackageVersion(package.getGenericKey(fileSpec))
|
||||
toPv = package.currentPv
|
||||
|
||||
patchChain = None
|
||||
if toPv and fromPv:
|
||||
patchChain = toPv.getPatchChain(fromPv)
|
||||
|
||||
return patchChain
|
||||
|
||||
def readPackageDescFile(self, descFilename):
|
||||
""" Reads a desc file associated with a particular package,
|
||||
and adds the package to self.packages. Returns the Package
|
||||
object, or None on failure. """
|
||||
|
||||
package = self.Package(Filename(descFilename), self)
|
||||
if not package.readDescFile(doProcessing = False):
|
||||
return None
|
||||
|
||||
self.packages.append(package)
|
||||
return package
|
||||
|
||||
def readContentsFile(self):
|
||||
""" Reads the contents.xml file at the beginning of
|
||||
processing. """
|
||||
|
||||
contentsFilename = Filename(self.installDir, 'contents.xml')
|
||||
doc = TiXmlDocument(contentsFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
# Couldn't read file.
|
||||
print("couldn't read %s" % (contentsFilename))
|
||||
return False
|
||||
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if xcontents:
|
||||
contentsSeq = SeqValue()
|
||||
contentsSeq.loadXml(xcontents)
|
||||
contentsSeq += 1
|
||||
contentsSeq.storeXml(xcontents)
|
||||
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
solo = xpackage.Attribute('solo')
|
||||
solo = int(solo or '0')
|
||||
filename = xpackage.Attribute('filename')
|
||||
if filename and not solo:
|
||||
filename = Filename(filename)
|
||||
package = self.Package(filename, self, xpackage)
|
||||
package.readDescFile(doProcessing = True)
|
||||
self.packages.append(package)
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
self.contentsDoc = doc
|
||||
|
||||
return True
|
||||
|
||||
def writeContentsFile(self):
|
||||
""" Writes the contents.xml file at the end of processing. """
|
||||
|
||||
# We also have to write the desc file for all packages that
|
||||
# might need it, because we might have changed some of them on
|
||||
# read.
|
||||
for package in self.packages:
|
||||
package.writeDescFile()
|
||||
|
||||
# The above writeDescFile() call should also update each
|
||||
# package's element within the contents.xml document, so all
|
||||
# we have to do now is write out the document.
|
||||
self.contentsDoc.SaveFile()
|
||||
|
||||
def getPackageVersion(self, key):
|
||||
""" Returns a shared PackageVersion object for the indicated
|
||||
key. """
|
||||
|
||||
packageName, platform, version, hostUrl, file = key
|
||||
|
||||
# We actually key on the hash, not the FileSpec itself.
|
||||
k = (packageName, platform, version, hostUrl, file.hash)
|
||||
pv = self.packageVersions.get(k, None)
|
||||
if not pv:
|
||||
pv = self.PackageVersion(*key)
|
||||
self.packageVersions[k] = pv
|
||||
return pv
|
||||
|
||||
def buildPatchChains(self):
|
||||
""" Builds up the chains of PackageVersions and the patchfiles
|
||||
that connect them. """
|
||||
|
||||
self.patchFilenames = {}
|
||||
|
||||
for package in self.packages:
|
||||
if not package.baseFile:
|
||||
# This package doesn't have any versions yet.
|
||||
continue
|
||||
|
||||
currentPv = self.getPackageVersion(package.getCurrentKey())
|
||||
package.currentPv = currentPv
|
||||
currentPv.packageCurrent = package
|
||||
currentPv.printName = package.currentFile.filename
|
||||
|
||||
basePv = self.getPackageVersion(package.getBaseKey())
|
||||
package.basePv = basePv
|
||||
basePv.packageBase = package
|
||||
basePv.printName = package.baseFile.filename
|
||||
|
||||
topPv = self.getPackageVersion(package.getTopKey())
|
||||
package.topPv = topPv
|
||||
topPv.packageTop = package
|
||||
|
||||
for patchfile in package.patches:
|
||||
self.recordPatchfile(patchfile)
|
||||
|
||||
def recordPatchfile(self, patchfile):
|
||||
""" Adds the indicated patchfile to the patch chains. """
|
||||
self.patchFilenames[patchfile.file.filename] = patchfile
|
||||
|
||||
fromPv = self.getPackageVersion(patchfile.getSourceKey())
|
||||
patchfile.fromPv = fromPv
|
||||
fromPv.toPatches.append(patchfile)
|
||||
|
||||
toPv = self.getPackageVersion(patchfile.getTargetKey())
|
||||
patchfile.toPv = toPv
|
||||
toPv.fromPatches.append(patchfile)
|
||||
toPv.printName = patchfile.file.filename
|
||||
|
||||
def processSomePackages(self, packageNames):
|
||||
""" Builds missing patches only for the named packages. """
|
||||
|
||||
remainingNames = packageNames[:]
|
||||
for package in self.packages:
|
||||
if package.packageName in packageNames:
|
||||
self.processPackage(package)
|
||||
if package.packageName in remainingNames:
|
||||
remainingNames.remove(package.packageName)
|
||||
|
||||
if remainingNames:
|
||||
print("Unknown packages: %s" % (remainingNames,))
|
||||
|
||||
def processAllPackages(self):
|
||||
""" Walks through the list of packages, and builds missing
|
||||
patches for each one. """
|
||||
|
||||
for package in self.packages:
|
||||
self.processPackage(package)
|
||||
|
||||
def processPackage(self, package):
|
||||
""" Builds missing patches for the indicated package. """
|
||||
|
||||
if not package.baseFile:
|
||||
# No versions.
|
||||
return
|
||||
|
||||
# What's the current version on the top of the tree?
|
||||
topPv = package.topPv
|
||||
currentPv = package.currentPv
|
||||
|
||||
if topPv != currentPv:
|
||||
# They're different, so build a new patch.
|
||||
filename = Filename(package.currentFile.filename + '.%s.patch' % (package.patchVersion))
|
||||
assert filename not in self.patchFilenames
|
||||
if not self.buildPatch(topPv, currentPv, package, filename):
|
||||
raise Exception("Couldn't build patch.")
|
||||
|
||||
def buildPatch(self, v1, v2, package, patchFilename):
|
||||
""" Builds a patch from PackageVersion v1 to PackageVersion
|
||||
v2, and stores it in patchFilename.pz. Returns true on
|
||||
success, false on failure."""
|
||||
|
||||
pathname = Filename(package.packageDir, patchFilename)
|
||||
if not self.buildPatchFile(v1.getFile(), v2.getFile(), pathname,
|
||||
v1.printName, v2.printName):
|
||||
return False
|
||||
|
||||
compressedPathname = Filename(pathname + '.pz')
|
||||
compressedPathname.unlink()
|
||||
if not compressFile(pathname, compressedPathname, 9):
|
||||
raise Exception("Couldn't compress patch.")
|
||||
pathname.unlink()
|
||||
|
||||
patchfile = self.Patchfile(package)
|
||||
patchfile.fromFile(package.packageDir, patchFilename + '.pz',
|
||||
v1.file, v2.file)
|
||||
package.patches.append(patchfile)
|
||||
package.anyChanges = True
|
||||
|
||||
self.recordPatchfile(patchfile)
|
||||
|
||||
return True
|
||||
|
||||
def buildPatchFile(self, origFilename, newFilename, patchFilename,
|
||||
printOrigName, printNewName):
|
||||
""" Creates a patch file from origFilename to newFilename,
|
||||
storing the result in patchFilename. Returns true on success,
|
||||
false on failure. """
|
||||
|
||||
if not origFilename.exists():
|
||||
# No original version to patch from.
|
||||
return False
|
||||
|
||||
print("Building patch from %s to %s" % (printOrigName, printNewName))
|
||||
patchFilename.unlink()
|
||||
p = Patchfile() # The C++ class
|
||||
if p.build(origFilename, newFilename, patchFilename):
|
||||
return True
|
||||
|
||||
# Unable to build a patch for some reason.
|
||||
patchFilename.unlink()
|
||||
return False
|
||||
@@ -1,95 +0,0 @@
|
||||
__all__ = ["ScanDirectoryNode"]
|
||||
|
||||
from panda3d.core import VirtualFileSystem, VirtualFileMountSystem, Filename, TiXmlDocument
|
||||
vfs = VirtualFileSystem.getGlobalPtr()
|
||||
|
||||
class ScanDirectoryNode:
|
||||
""" This class is used to scan a list of files on disk. """
|
||||
|
||||
def __init__(self, pathname, ignoreUsageXml = False):
|
||||
self.pathname = pathname
|
||||
self.filenames = []
|
||||
self.fileSize = 0
|
||||
self.nested = []
|
||||
self.nestedSize = 0
|
||||
|
||||
xusage = None
|
||||
if not ignoreUsageXml:
|
||||
# Look for a usage.xml file in this directory. If we find
|
||||
# one, we read it for the file size and then stop here, as
|
||||
# an optimization.
|
||||
usageFilename = Filename(pathname, 'usage.xml')
|
||||
doc = TiXmlDocument(usageFilename.toOsSpecific())
|
||||
if doc.LoadFile():
|
||||
xusage = doc.FirstChildElement('usage')
|
||||
if xusage:
|
||||
diskSpace = xusage.Attribute('disk_space')
|
||||
try:
|
||||
diskSpace = int(diskSpace or '')
|
||||
except ValueError:
|
||||
diskSpace = None
|
||||
if diskSpace is not None:
|
||||
self.fileSize = diskSpace
|
||||
return
|
||||
|
||||
files = vfs.scanDirectory(self.pathname)
|
||||
if files is None:
|
||||
files = []
|
||||
for vfile in files:
|
||||
if hasattr(vfile, 'getMount'):
|
||||
if not isinstance(vfile.getMount(), VirtualFileMountSystem):
|
||||
# Not a real file; ignore it.
|
||||
continue
|
||||
|
||||
if vfile.isDirectory():
|
||||
# A nested directory.
|
||||
subdir = ScanDirectoryNode(vfile.getFilename(), ignoreUsageXml = ignoreUsageXml)
|
||||
self.nested.append(subdir)
|
||||
self.nestedSize += subdir.getTotalSize()
|
||||
|
||||
elif vfile.isRegularFile():
|
||||
# A nested file.
|
||||
self.filenames.append(vfile.getFilename())
|
||||
self.fileSize += vfile.getFileSize()
|
||||
|
||||
else:
|
||||
# Some other wacky file thing.
|
||||
self.filenames.append(vfile.getFilename())
|
||||
|
||||
if xusage:
|
||||
# Now update the usage.xml file with the newly-determined
|
||||
# disk space.
|
||||
xusage.SetAttribute('disk_space', str(self.getTotalSize()))
|
||||
tfile = Filename.temporary(str(pathname), '.xml')
|
||||
if doc.SaveFile(tfile.toOsSpecific()):
|
||||
tfile.renameTo(usageFilename)
|
||||
|
||||
def getTotalSize(self):
|
||||
return self.nestedSize + self.fileSize
|
||||
|
||||
def extractSubdir(self, pathname):
|
||||
""" Finds the ScanDirectoryNode within this node that
|
||||
corresponds to the indicated full pathname. If it is found,
|
||||
removes it from its parent, and returns it. If it is not
|
||||
found, returns None. """
|
||||
|
||||
# We could be a little smarter here, but why bother. Just
|
||||
# recursively search all children.
|
||||
for subdir in self.nested:
|
||||
if subdir.pathname == pathname:
|
||||
self.nested.remove(subdir)
|
||||
self.nestedSize -= subdir.getTotalSize()
|
||||
return subdir
|
||||
|
||||
result = subdir.extractSubdir(pathname)
|
||||
if result:
|
||||
self.nestedSize -= result.getTotalSize()
|
||||
if subdir.getTotalSize() == 0:
|
||||
# No other files in the subdirectory that contains
|
||||
# this package; remove it too.
|
||||
self.nested.remove(subdir)
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
__all__ = ["SeqValue"]
|
||||
|
||||
class SeqValue:
|
||||
|
||||
""" This represents a sequence value read from a contents.xml
|
||||
file, either from the <contents> or the <package> section. It's
|
||||
represented as series of dotted integers in the xml file, and
|
||||
stored internally as a tuple of integers.
|
||||
|
||||
It may be incremented, which increments only the last integer in
|
||||
the series; or it may be compared with another SeqValue, which
|
||||
compares all of the integers componentwise. """
|
||||
|
||||
def __init__(self, value = None):
|
||||
self.value = ()
|
||||
if value is not None:
|
||||
self.set(value)
|
||||
|
||||
def set(self, value):
|
||||
""" Sets the seq from the indicated value of unspecified
|
||||
type. """
|
||||
if isinstance(value, tuple):
|
||||
self.setFromTuple(value)
|
||||
elif isinstance(value, str):
|
||||
self.setFromString(value)
|
||||
else:
|
||||
raise TypeError('Invalid sequence type: %s' % (value,))
|
||||
|
||||
def setFromTuple(self, value):
|
||||
""" Sets the seq from the indicated tuple of integers. """
|
||||
assert isinstance(value, tuple)
|
||||
self.value = value
|
||||
|
||||
def setFromString(self, value):
|
||||
""" Sets the seq from the indicated string of dot-separated
|
||||
integers. Raises ValueError on error. """
|
||||
assert isinstance(value, str)
|
||||
|
||||
self.value = ()
|
||||
if value:
|
||||
value = value.split('.')
|
||||
value = map(int, value)
|
||||
self.value = tuple(value)
|
||||
|
||||
def loadXml(self, xelement, attribute = 'seq'):
|
||||
""" Reads the seq from the indicated XML element. Returns
|
||||
true if loaded, false if not given or if there was an
|
||||
error. """
|
||||
|
||||
self.value = ()
|
||||
value = xelement.Attribute(attribute)
|
||||
if value:
|
||||
try:
|
||||
self.setFromString(value)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def storeXml(self, xelement, attribute = 'seq'):
|
||||
""" Adds the seq to the indicated XML element. """
|
||||
if self.value:
|
||||
value = '.'.join(map(str, self.value))
|
||||
xelement.SetAttribute(attribute, value)
|
||||
|
||||
def __add__(self, inc):
|
||||
""" Increments the seq value, returning the new value. """
|
||||
if not self.value:
|
||||
value = (1,)
|
||||
else:
|
||||
value = self.value[:-1] + (self.value[-1] + inc,)
|
||||
return SeqValue(value)
|
||||
|
||||
def __cmp__(self, other):
|
||||
""" Compares to another seq value. """
|
||||
return cmp(self.value, other.value)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.value < other.value
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.value > other.value
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.value)
|
||||
|
||||
def __str__(self):
|
||||
return 'SeqValue%s' % (repr(self.value))
|
||||
@@ -1,4 +0,0 @@
|
||||
"""
|
||||
This package provides the Python interface to functionality relating to
|
||||
the Panda3D Runtime environment.
|
||||
"""
|
||||
@@ -1,124 +0,0 @@
|
||||
from panda3d.core import getModelPath, Filename, ConfigVariableFilename, DSearchPath, ExecutionEnvironment, PandaSystem
|
||||
|
||||
# This file defines a number of standard "packages" that correspond to
|
||||
# a Panda3D distribution. These packages are built by passing this
|
||||
# file to the ppackage utility, either as a packaged application, or
|
||||
# as the module direct.p3d.ppackage.
|
||||
|
||||
# The packages in this file define the "Core API". This is the second
|
||||
# installed piece of the three-part plugin system (the plugin, the
|
||||
# core API, Panda3D).
|
||||
|
||||
# These packages are downloaded directly by the plugin, from the host
|
||||
# specified by the value of PANDA_PACKAGE_HOST_URL compiled into the
|
||||
# plugin. Thus, these packages are inextricably tied to the
|
||||
# particular plugin they have been built with. They do not have to be
|
||||
# present on a server that hosts a version of Panda3D for download,
|
||||
# just on the server that hosts the plugin itself. These packages do
|
||||
# not need to be updated with each new version of Panda3D.
|
||||
|
||||
# Also see panda3d.pdef.
|
||||
|
||||
class coreapi(solo):
|
||||
# The special "coreapi" package. As a "solo", this is just a
|
||||
# single .dll (or dylib, or whatever).
|
||||
setVer(PandaSystem.getP3dCoreapiVersionString())
|
||||
file('p3d_plugin.dll')
|
||||
|
||||
class images(package):
|
||||
# The default startup images are stored as their own package.
|
||||
names = ['download', 'failed', 'play_click', 'play_ready', 'play_rollover',
|
||||
'auth_click', 'auth_ready', 'auth_rollover']
|
||||
configDict = {}
|
||||
|
||||
# Construct a search path to look for the images.
|
||||
search = DSearchPath()
|
||||
|
||||
# First on the path: an explicit $PLUGIN_IMAGES env var.
|
||||
if ExecutionEnvironment.hasEnvironmentVariable('PLUGIN_IMAGES'):
|
||||
search.appendDirectory(Filename.expandFrom('$PLUGIN_IMAGES'))
|
||||
|
||||
# Next on the path: the models/plugin_images directory within the
|
||||
# current directory.
|
||||
search.appendDirectory('models/plugin_images')
|
||||
|
||||
# Finally on the path: models/plugin_images within the model
|
||||
# search path.
|
||||
for dir in getModelPath().getDirectories():
|
||||
search.appendDirectory(Filename(dir, 'plugin_images'))
|
||||
|
||||
for name in names:
|
||||
# Look for a png image first.
|
||||
basename = '%s.png' % (name)
|
||||
filename = Filename(basename)
|
||||
found = filename.resolveFilename(search)
|
||||
if not found:
|
||||
# Then try a jpeg image.
|
||||
basename = '%s.jpg' % (name)
|
||||
filename = Filename(basename)
|
||||
found = filename.resolveFilename(search)
|
||||
|
||||
if found:
|
||||
# Add the image file to the package
|
||||
file(filename, newName = basename, extract = True)
|
||||
|
||||
# And set the config variable to reference it.
|
||||
token = '%s_img' % (name)
|
||||
configDict[token] = basename
|
||||
else:
|
||||
print("Could not locate %s" % (filename))
|
||||
|
||||
# Also make a few special cases. We use the same default image
|
||||
# for many of the states.
|
||||
download = configDict.get('download_img', None)
|
||||
if download:
|
||||
configDict['ready_img'] = download
|
||||
configDict['unauth_img'] = download
|
||||
configDict['launch_img'] = download
|
||||
configDict['active_img'] = download
|
||||
|
||||
config(**configDict)
|
||||
|
||||
class certlist(package):
|
||||
# This package holds any certificates that should be considered
|
||||
# pre-approved by the plugin vendor. If you build and host your
|
||||
# own version of the Panda3D plugin, you can add your own
|
||||
# certificates to this list. The certificates in this package
|
||||
# will be assumed to have been approved by the user when he/she
|
||||
# installed the plugin.
|
||||
|
||||
# They should be PEM-encoded and their filenames must end in the
|
||||
# ".pem" or ".crt" extension, and they should be added with the
|
||||
# extract = True flag so they will be extracted to disk.
|
||||
pass
|
||||
|
||||
|
||||
class p3dcert(package):
|
||||
# This special application, used to pop up a dialog to prompt the
|
||||
# user to accept or deny unknown applications, is its own package.
|
||||
config(display_name = "Authorization Dialog")
|
||||
|
||||
if platform.startswith('osx'):
|
||||
# On Mac, we package up a P3DCert.app bundle. This includes
|
||||
# specifications in the plist file to avoid creating a dock
|
||||
# icon and stuff.
|
||||
|
||||
# Find p3dcert.plist in the direct source tree.
|
||||
import direct
|
||||
plist = Filename(direct.__path__[0], 'plugin/p3dcert.plist')
|
||||
makeBundle('P3DCert.app', plist, executable = 'p3dcert')
|
||||
|
||||
else:
|
||||
# Anywhere else, we just ship the executable file p3dcert.exe.
|
||||
file('p3dcert.exe', required = True)
|
||||
|
||||
|
||||
class p3dembed(package):
|
||||
# This contains the p3dembed executable, that is used by
|
||||
# pdeploy to generate a self-extracting .p3d executable.
|
||||
|
||||
config(platform_specific = True)
|
||||
file('p3dembed.exe', required = True)
|
||||
|
||||
if platform.startswith('win'):
|
||||
file('p3dembedw.exe', required = True)
|
||||
@@ -1,127 +0,0 @@
|
||||
/* Filename: p3dWrapper.c
|
||||
* Created by: rdb (16Jan10)
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* p3dWrapper is a small wrapper executable that locates a .p3d file
|
||||
in the same directory as this executable file, with the same name
|
||||
(except .p3d instead of .exe of course). It is only meant to be
|
||||
used on Windows system, it is not needed on Unix-like systems. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
#include <assert.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
/* It makes sense to use "App Paths\panda3d.exe". However, Microsoft
|
||||
decided in their infinite wisdom to disable Redirection for that
|
||||
key from Windows 7 onward, so we can't rely on it producing a
|
||||
result appropriate to the right architecture when both the 32-bit
|
||||
and 64-bit versions of the runtime are installed. Beh. */
|
||||
|
||||
#define UNINST_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Panda3D Game Engine"
|
||||
|
||||
int main (int argc, char* argv[]) {
|
||||
int i;
|
||||
char buffer[BUFFER_SIZE];
|
||||
char* p3dfile;
|
||||
char* runtime = NULL;
|
||||
DWORD size;
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
char *cmd;
|
||||
char *newcmd;
|
||||
HKEY hKey = 0;
|
||||
char buf[1024] = {0};
|
||||
DWORD dwType = REG_SZ;
|
||||
DWORD dwBufSize = sizeof(buf);
|
||||
size = GetModuleFileName(NULL, buffer, BUFFER_SIZE);
|
||||
assert (size > 0);
|
||||
|
||||
/* Chop off the .exe and replace it by .p3d. */
|
||||
p3dfile = (char*) _alloca(size + 1);
|
||||
memcpy(p3dfile, buffer, size);
|
||||
p3dfile[size] = 0;
|
||||
memcpy(p3dfile + size - 3, "p3d", 3);
|
||||
|
||||
/* Find the location of panda3d.exe using the registry path. */
|
||||
#ifdef _WIN64
|
||||
/* If we're on 64-bit Windows, try the 64-bit registry first. */
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, UNINST_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueEx(hKey, "DisplayIcon", 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
|
||||
char *slash = strrchr(buf, '\\');
|
||||
if (slash != NULL) {
|
||||
strcpy(slash, "\\panda3d.exe");
|
||||
runtime = buf;
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* On 32-bit Windows, or no 64-bit Runtime installed. Try 32-bit registry. */
|
||||
if (runtime == NULL) {
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, UNINST_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueEx(hKey, "DisplayIcon", 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
|
||||
char *slash = strrchr(buf, '\\');
|
||||
if (slash != NULL) {
|
||||
strcpy(slash, "\\panda3d.exe");
|
||||
runtime = buf;
|
||||
}
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
}
|
||||
|
||||
/* Backward compatibility: Runtime 1.0.4 and below looked for the below key, even though the
|
||||
above keys should work fine, but let's just be certain.
|
||||
Find the Panda3D applet\DefaultIcon key and extract the path to the runtime from there. */
|
||||
if (runtime == NULL) {
|
||||
if (RegOpenKey(HKEY_CLASSES_ROOT, "Panda3D applet\\DefaultIcon", &hKey) == ERROR_SUCCESS) {
|
||||
if (RegQueryValueEx(hKey, 0, 0, &dwType, (BYTE*) buf, &dwBufSize) == ERROR_SUCCESS) {
|
||||
char *slash = strrchr(buf, '\\');
|
||||
if (slash != NULL) {
|
||||
strcpy(slash, "\\panda3d.exe");
|
||||
runtime = buf;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Failed to read registry key. Try reinstalling the Panda3D Runtime.\n");
|
||||
return 1;
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
} else {
|
||||
fprintf(stderr, "The Panda3D Runtime does not appear to be installed!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime == NULL) {
|
||||
fprintf(stderr, "Failed to find panda3d.exe in registry. Try reinstalling the Panda3D Runtime.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Build the command-line and run panda3d.exe. */
|
||||
cmd = GetCommandLine();
|
||||
newcmd = (char*) _alloca(strlen(runtime) + strlen(p3dfile) + strlen(cmd) - strlen (argv[0]) + 7);
|
||||
sprintf(newcmd, "\"%s\" \"%s\" %s", runtime, p3dfile, cmd + strlen(argv[0]));
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
if (CreateProcess(runtime, newcmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This command will pack a Panda application, consisting of a directory
|
||||
tree of .py files and models, into a p3d file for convenient
|
||||
distribution. The resulting p3d file can be run by the Panda3D
|
||||
runtime executable, or by the Panda3D web browser plugin.
|
||||
|
||||
This command will build p3d files that reference Panda3D %s,
|
||||
from host %s .
|
||||
|
||||
Also see ppackage, a more powerful (but more complex) tool that can
|
||||
also be used to build p3d applications, using a pdef description file.
|
||||
|
||||
Usage:
|
||||
|
||||
%s [opts] -o app.p3d
|
||||
|
||||
Options:
|
||||
|
||||
-o app.p3d
|
||||
Specify the name of the p3d file to generate. This is required.
|
||||
|
||||
-d application_root
|
||||
Specify the root directory of the application source; this is a
|
||||
directory tree that contains all of your .py files and models.
|
||||
If this is omitted, the default is the current directory.
|
||||
|
||||
-m main.py
|
||||
Names the Python file that begins the application. This should
|
||||
be a file within the root directory. If this is omitted, the
|
||||
default is a file named "main.py", or if there is only one Python
|
||||
file present, it is used. If this file contains a function
|
||||
called main(), that function will be called after importing it
|
||||
(this is preferable to having the module start itself immediately
|
||||
upon importing).
|
||||
|
||||
-S file.crt[,chain.crt[,file.key[,\"password\"]]]
|
||||
Signs the resulting p3d with the indicated certificate. You may
|
||||
specify the signing certificate, the optional authorization
|
||||
chain, and the private key in three different files, or they may
|
||||
all be combined in the first file. If the private key is
|
||||
encrypted, the password will be required to decrypt it.
|
||||
|
||||
-e ext
|
||||
Adds a new extension to be processed as a generic, compressible
|
||||
file type. Do not include the leading dot. Files matching this
|
||||
extension found within the root directory will be automatically
|
||||
added to the p3d file, in compressed form. This option may be
|
||||
repeated as necessary.
|
||||
|
||||
-n ext
|
||||
Adds a new extension to be processed as a noncompressible file
|
||||
type. Files matching this extension will be added to the p3d
|
||||
file, in their original, uncompressed form. You should use this
|
||||
instead of -e for files that are uncompressible by their nature
|
||||
(e.g. mpg files). This option may be repeated as necessary.
|
||||
|
||||
-x ext
|
||||
Marks files with the given extensions of needing to be physically
|
||||
extracted to disk before they can be loaded. This is used for
|
||||
file types that cannot be loaded via the virtual file system,
|
||||
such as .ico files on Windows.
|
||||
This option is currently only implemented when deploying the
|
||||
application with pdeploy.
|
||||
|
||||
-p python_lib_dir
|
||||
Adds a directory to search for additional Python modules. You
|
||||
can use this to add your system's Python path, to allow packp3d
|
||||
to find any system modules not included in the standard Panda3D
|
||||
release, but your version of Python must match this one (%s).
|
||||
This option may be repeated to add multiple directories.
|
||||
|
||||
-c config=value
|
||||
Sets the indicated config flag in the application. This option
|
||||
may be repeated as necessary.
|
||||
|
||||
-r package[,version[,hostURL]]
|
||||
Names an additional package that this application requires at
|
||||
startup time. The default package is 'panda3d'; you may repeat
|
||||
this option to indicate dependencies on additional packages.
|
||||
|
||||
-s search_dir
|
||||
Additional directories to search for previously-built packages.
|
||||
This option may be repeated as necessary. These directories may
|
||||
also be specified with the pdef-path Config.prc variable.
|
||||
|
||||
-D
|
||||
Sets the allow_python_dev flag in the application. This enables
|
||||
additional runtime debug operations, particularly the -i option
|
||||
to the panda3d command, which enables a live Python prompt within
|
||||
the application's environment. Setting this flag may be useful
|
||||
to develop an application initially, but should not be set on an
|
||||
application intended for deployment.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import glob
|
||||
from direct.p3d import Packager
|
||||
from panda3d.core import *
|
||||
|
||||
# Temp hack for debugging.
|
||||
#from direct.p3d.AppRunner import dummyAppRunner; dummyAppRunner()
|
||||
|
||||
class ArgumentError(Exception):
|
||||
pass
|
||||
|
||||
def makePackedApp(args):
|
||||
opts, args = getopt.getopt(args, 'o:d:m:S:e:n:x:p:c:r:s:Dh')
|
||||
|
||||
packager = Packager.Packager()
|
||||
|
||||
appFilename = None
|
||||
root = Filename('.')
|
||||
main = None
|
||||
configFlags = []
|
||||
requires = []
|
||||
allowPythonDev = False
|
||||
|
||||
for option, value in opts:
|
||||
if option == '-o':
|
||||
appFilename = Filename.fromOsSpecific(value)
|
||||
elif option == '-d':
|
||||
root = Filename.fromOsSpecific(value)
|
||||
elif option == '-m':
|
||||
main = value
|
||||
elif option == '-S':
|
||||
tokens = value.split(',')
|
||||
while len(tokens) < 4:
|
||||
tokens.append('')
|
||||
certificate, chain, pkey, password = tokens[:4]
|
||||
packager.signParams.append((Filename.fromOsSpecific(certificate),
|
||||
Filename.fromOsSpecific(chain),
|
||||
Filename.fromOsSpecific(pkey),
|
||||
Filename.fromOsSpecific(password)))
|
||||
elif option == '-e':
|
||||
packager.binaryExtensions.append(value)
|
||||
elif option == '-n':
|
||||
packager.uncompressibleExtensions.append(value)
|
||||
elif option == '-x':
|
||||
packager.extractExtensions.append(value)
|
||||
elif option == '-p':
|
||||
sys.path.append(value)
|
||||
elif option == '-c':
|
||||
configFlags.append(value.split('=', 1))
|
||||
elif option == '-r':
|
||||
tokens = value.split(',')
|
||||
while len(tokens) < 3:
|
||||
tokens.append('')
|
||||
name, version, host = tokens[:3]
|
||||
requires.append((name, version, host))
|
||||
elif option == '-s':
|
||||
packager.installSearch.append(Filename.fromOsSpecific(value))
|
||||
elif option == '-D':
|
||||
allowPythonDev = True
|
||||
elif option == '-h':
|
||||
print(usageText % (
|
||||
PandaSystem.getPackageVersionString(),
|
||||
PandaSystem.getPackageHostUrl(),
|
||||
os.path.split(sys.argv[0])[1],
|
||||
'%s.%s' % (sys.version_info[0], sys.version_info[1])))
|
||||
sys.exit(0)
|
||||
|
||||
if not appFilename:
|
||||
raise ArgumentError("No target app specified. Use:\n %s -o app.p3d\nUse -h to get more usage information." % (os.path.split(sys.argv[0])[1]))
|
||||
|
||||
if args:
|
||||
raise ArgumentError("Extra arguments on command line.")
|
||||
|
||||
if appFilename.getExtension() != 'p3d':
|
||||
raise ArgumentError('Application filename must end in ".p3d".')
|
||||
|
||||
appDir = Filename(appFilename.getDirname())
|
||||
if not appDir:
|
||||
appDir = Filename('.')
|
||||
appBase = appFilename.getBasenameWoExtension()
|
||||
|
||||
if main:
|
||||
main = Filename.fromOsSpecific(main)
|
||||
main.makeAbsolute(root)
|
||||
else:
|
||||
main = Filename(root, 'main.py')
|
||||
if not main.exists():
|
||||
main = glob.glob(os.path.join(root.toOsSpecific(), '*.py'))
|
||||
if len(main) == 0:
|
||||
raise ArgumentError('No Python files in root directory.')
|
||||
elif len(main) > 1:
|
||||
raise ArgumentError('Multiple Python files in root directory; specify the main application with -m "main".')
|
||||
|
||||
main = Filename.fromOsSpecific(os.path.split(main[0])[1])
|
||||
main.makeAbsolute(root)
|
||||
|
||||
packager.installDir = appDir
|
||||
packager.allowPythonDev = allowPythonDev
|
||||
|
||||
# Put the root directory on the front of the model-path, so that
|
||||
# any texture references in egg or bam files that reference
|
||||
# textures from the top of the root directory will be properly
|
||||
# resolved.
|
||||
getModelPath().prependDirectory(root)
|
||||
|
||||
try:
|
||||
packager.setup()
|
||||
packager.beginPackage(appBase, p3dApplication = True)
|
||||
|
||||
# Pre-require panda3d, to give a less-confusing error message
|
||||
# if one of our requirements pulls in a wrong version of
|
||||
# panda3d.
|
||||
if 'panda3d' not in [t[0] for t in requires]:
|
||||
packager.do_require('panda3d')
|
||||
|
||||
for name, version, host in requires:
|
||||
packager.do_require(name, version = version, host = host)
|
||||
|
||||
if configFlags:
|
||||
packager.do_config(**dict(configFlags))
|
||||
|
||||
packager.do_dir(root)
|
||||
packager.do_main(main)
|
||||
packager.endPackage()
|
||||
packager.close()
|
||||
|
||||
except Packager.PackagerError:
|
||||
# Just print the error message and exit gracefully.
|
||||
inst = sys.exc_info()[1]
|
||||
print(inst.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
makePackedApp(sys.argv[1:])
|
||||
except ArgumentError as e:
|
||||
print(e.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
@@ -1,518 +0,0 @@
|
||||
import sys
|
||||
from panda3d.core import Filename, PandaSystem, getModelPath
|
||||
|
||||
# This file defines a number of standard "packages" that correspond to
|
||||
# a Panda3D distribution. These packages are built by passing this
|
||||
# file to the ppackage utility, either as a packaged application, or
|
||||
# as the module direct.p3d.ppackage.
|
||||
|
||||
# The packages in this file define Panda3D itself. This is the third
|
||||
# installed piece of the three-part plugin system (the plugin, the
|
||||
# core API, Panda3D).
|
||||
|
||||
# When needed, these packages are downloaded by the core API, from the
|
||||
# host URL specified in a given p3d file, and not from any hardcoded
|
||||
# URL. Thus, any custom version of Panda3D may be hosted on any
|
||||
# server in the world, and any version of the plugin can download it.
|
||||
|
||||
# Also see coreapi.pdef.
|
||||
|
||||
class panda3d(package):
|
||||
# The main Panda3D package. Contains Python and most of the graphics
|
||||
# code in Panda3D.
|
||||
|
||||
config(display_name = "Panda3D")
|
||||
|
||||
# First, add the minimum startup files for a Panda3D package.
|
||||
# These are files that the Panda3D runtime will explicitly look
|
||||
# for by name in order to get itself bootstrapped.
|
||||
setupPanda3D()
|
||||
|
||||
# These are Python modules that are needed by most Panda3D
|
||||
# applications. It doesn't matter too much if we miss one or two
|
||||
# here, since any module imported by any of this code will
|
||||
# automatically be included as well, and we end up with a pretty
|
||||
# complete list that way.
|
||||
module('direct.directbase.DirectStart',
|
||||
# Don't want to include all files in direct.p3d, because
|
||||
# that picks up the runtime scripts too, which are their
|
||||
# own p3d files below.
|
||||
'direct.p3d.AppRunner',
|
||||
'direct.p3d.DWBPackageInstaller',
|
||||
'direct.actor.*',
|
||||
'direct.controls.*',
|
||||
'direct.directdevices.*',
|
||||
'direct.directnotify.*',
|
||||
'direct.directtools.*',
|
||||
'direct.directutil.*',
|
||||
'direct.distributed.*',
|
||||
'direct.filter.*',
|
||||
'direct.fsm.*',
|
||||
'direct.gui.*',
|
||||
'direct.interval.*',
|
||||
'direct.particles.*',
|
||||
'direct.showbase.*',
|
||||
'direct.showutil.*',
|
||||
'direct.stdpy.*',
|
||||
'direct.task.*')
|
||||
|
||||
# Keep these modules for backward compatibility.
|
||||
module('pandac.PandaModules', 'pandac.extension_native_helpers')
|
||||
|
||||
module('panda3d.core',
|
||||
'panda3d.direct',
|
||||
'panda3d.fx',
|
||||
'panda3d.physics')
|
||||
|
||||
# Include various standard Python encodings. The rest is in morepy.
|
||||
module('encodings', 'encodings.aliases', 'encodings.undefined',
|
||||
'encodings.utf_8', 'encodings.ascii', 'encodings.mbcs',
|
||||
'encodings.latin_1', 'io')
|
||||
if sys.version_info < (3, 0):
|
||||
module('encodings.string_escape')
|
||||
|
||||
# pandac.PandaModules pulls in other Panda3D libs automatically.
|
||||
# Exclude these Panda3D libs; they are big and many applications don't
|
||||
# use them. We define them as separate, optional packages, below,
|
||||
# except for skel - this is useless in a shipped game anyways.
|
||||
excludeModule('panda3d.egg')
|
||||
excludeModule('panda3d.ode')
|
||||
excludeModule('panda3d.bullet')
|
||||
excludeModule('panda3d.vision')
|
||||
excludeModule('panda3d.skel')
|
||||
excludeModule('panda3d.physx')
|
||||
excludeModule('panda3d.ai')
|
||||
excludeModule('panda3d.vrpn')
|
||||
|
||||
excludeModule('libpandaegg')
|
||||
excludeModule('libpandaode')
|
||||
excludeModule('libpandabullet')
|
||||
excludeModule('libp3vision')
|
||||
excludeModule('libpandaskel')
|
||||
excludeModule('libpandaphysx')
|
||||
excludeModule('libpandaai')
|
||||
excludeModule('libp3vrpn')
|
||||
|
||||
# Exclude these GUI toolkits; they're big, and many applications don't
|
||||
# use them. We define them as separate, optional packages, below.
|
||||
excludeModule('wx',
|
||||
'direct.showbase.WxGlobal')
|
||||
|
||||
excludeModule('Tkinter', 'tkinter', 'Pmw', 'tkinter.simpledialog',
|
||||
'direct.showbase.TkGlobal',
|
||||
'direct.tkpanels', 'direct.tkwidgets',
|
||||
'tkCommonDialog', 'tkMessageBox', 'tkSimpleDialog')
|
||||
|
||||
excludeModule('MySQLdb', 'MySQLdb.connections', 'MySQLdb.constants',
|
||||
'MySQLdb.converters', '_mysql')
|
||||
|
||||
# Some code in distributed conditionally imports these.
|
||||
excludeModule('otp.ai', 'otp.ai.AIZoneData')
|
||||
|
||||
# Most of the core Panda3D DLL's will be included implicitly due to
|
||||
# being referenced by the above Python code. Here we name a few more
|
||||
# that are also needed, but aren't referenced by any code. Again,
|
||||
# note that the .dll extension is automatically replaced with the
|
||||
# platform-specific extension for an executable.
|
||||
file('libpandagl.dll', 'libp3tinydisplay.dll')
|
||||
if platform.startswith('win'):
|
||||
file('libpandadx9.dll')
|
||||
|
||||
# A basic config file is needed to lay some some fundamental runtime
|
||||
# variables.
|
||||
if platform.startswith('win'):
|
||||
auxDisplays = """
|
||||
aux-display pandagl
|
||||
aux-display pandadx9
|
||||
aux-display p3tinydisplay
|
||||
"""
|
||||
else:
|
||||
auxDisplays = """
|
||||
aux-display pandagl
|
||||
aux-display p3tinydisplay
|
||||
"""
|
||||
|
||||
file('Config.prc', extract = True, text = """
|
||||
plugin-path $PANDA3D_ROOT
|
||||
default-model-extension .bam
|
||||
cull-bin gui-popup 60 unsorted
|
||||
""" + auxDisplays)
|
||||
|
||||
class morepy(package):
|
||||
# Additional Python modules provided by the standard Python
|
||||
# library. Include this package to get the full suite of standard
|
||||
# Python functionality.
|
||||
|
||||
config(display_name = "Python standard library")
|
||||
require('panda3d')
|
||||
|
||||
module('string', 're', 'struct', 'difflib',
|
||||
'textwrap', 'codecs', 'unicodedata', 'stringprep', 'datetime',
|
||||
'calendar', 'collections', 'heapq', 'bisect', 'array',
|
||||
'sched', 'queue', 'weakref', 'types', 'copy', 'pprint',
|
||||
'numbers', 'math', 'cmath', 'decimal', 'fractions',
|
||||
'random', 'itertools', 'functools', 'operator', 'os.path',
|
||||
'fileinput', 'filecmp', 'tempfile', 'glob', 'fnmatch', 'linecache',
|
||||
'shutil', 'macpath', 'pickle', 'shelve', 'marshal', 'zlib',
|
||||
'gzip', 'bz2', 'zipfile', 'tarfile', 'csv', 'netrc',
|
||||
'xdrlib', 'plistlib', 'hashlib', 'hmac', 'os',
|
||||
'time', 'optparse', 'getopt', 'logging', 'logging.*')
|
||||
module('getpass', 'curses', 'curses.textpad', 'curses.wrapper',
|
||||
'curses.ascii', 'curses.panel', 'platform', 'errno',
|
||||
'ctypes', 'select', 'threading', 'dummy_threading', 'dummy_thread',
|
||||
'multiprocessing', 'mmap', 'readline', 'rlcompleter', 'subprocess',
|
||||
'socket', 'ssl', 'signal', 'asyncore', 'asynchat', 'email', 'json',
|
||||
'mailcap', 'mailbox', 'mimetypes', 'base64', 'binhex', 'binascii',
|
||||
'quopri', 'uu', 'xml.parsers.expat', 'xml.dom', 'xml.dom.minidom',
|
||||
'xml.dom.pulldom', 'xml.sax', 'xml.sax.handler',
|
||||
'xml.sax.saxutils', 'xml.sax.xmlreader',
|
||||
'xml.etree.ElementTree', 'webbrowser', 'cgi', 'cgitb')
|
||||
module('wsgiref', 'ftplib', 'poplib', 'imaplib', 'nntplib', 'smtplib',
|
||||
'smtpd', 'telnetlib', 'uuid', 'audioop', 'aifc', 'sunau',
|
||||
'wave', 'chunk', 'colorsys', 'imghdr', 'sndhdr',
|
||||
'ossaudiodev', 'gettext', 'locale', 'cmd', 'shlex',
|
||||
'pydoc', 'doctest', 'unittest', 'test',
|
||||
'bdb', 'pdb', 'timeit', 'trace', 'sys')
|
||||
module('warnings', 'contextlib', 'abc',
|
||||
'atexit', 'traceback', '__future__', 'gc', 'inspect',
|
||||
'site', 'fpectl', 'code', 'codeop', 'zipimport', 'pkgutil',
|
||||
'modulefinder', 'runpy', 'parser', 'symtable', 'symbol',
|
||||
'token', 'keyword', 'tokenize', 'tabnanny', 'pyclbr',
|
||||
'py_compile', 'compileall', 'dis', 'pickletools',
|
||||
'distutils', 'msilib', 'msvcrt', 'winsound', 'posix', 'pwd', 'spwd',
|
||||
'grp', 'crypt', 'termios', 'tty', 'pty', 'fcntl', 'pipes',
|
||||
'resource', 'nis', 'syslog', 'ast')
|
||||
|
||||
# Handle module name differences between Python 2 and Python 3 (see PEP3108)
|
||||
if sys.version_info[0] < 3:
|
||||
# Deprecated modules that were removed in Python 3
|
||||
module('posixfile', 'rfc822', 'mimetools','MimeWriter', 'mimify',
|
||||
'multifile', 'sets', 'md5', 'sha', 'imp', 'formatter')
|
||||
# Mac-specific modules that were removed
|
||||
module('autoGIL', 'ColorPicker', 'EasyDialogs', 'findertools',
|
||||
'FrameWork', 'ic', 'MacOS', 'macostools')
|
||||
# Removed because they were hardly used
|
||||
module('imputil', 'mutex', 'user', 'new')
|
||||
# Removed because they were obsolete
|
||||
module('Bastion', 'rexec', 'commands', 'dircache', 'dl', 'fpformat',
|
||||
'htmllib', 'imageop', 'mhlib', 'popen2', 'sgmllib', 'stat',
|
||||
'statvfs', 'thread', 'UserDict', 'UserList', 'UserString',
|
||||
'future_builtins', 'hotshot', 'bsddb')
|
||||
# Renamed to fix PEP8 violations
|
||||
module('_winreg', 'ConfigParser', 'copy_reg', 'SocketServer')
|
||||
# C and Python implementations of the same interface were merged
|
||||
module('cPickle', 'cStringIO', 'StringIO')
|
||||
# Renamed because of poorly chosen names
|
||||
module('repr', 'test.test_support', '__builtins__')
|
||||
# Modules that got grouped under packages
|
||||
module('anydbm', 'whichdb', 'dbm', 'dumbdbm', 'gdbm', 'dbhash')
|
||||
module('HTMLParser', 'htmlentitydefs')
|
||||
module('BaseHTTPServer', 'SimpleHTTPServer', 'cookielib', 'Cookie',
|
||||
'CGIHTTPServer', 'httplib')
|
||||
module('urllib', 'urllib2', 'urlparse', 'robotparser')
|
||||
module('xmlrpclib', 'SimpleXMLRPCServer', 'DocXMLRPCServer')
|
||||
else:
|
||||
# Renamed to fix PEP8 violations
|
||||
module('winreg', 'configparser', 'copyreg', 'socketserver')
|
||||
# Renamed because of poorly chosen names
|
||||
module('reprlib', 'test.support', 'builtins')
|
||||
# Modules that got grouped under packages
|
||||
module('dbm')
|
||||
module('html')
|
||||
module('http')
|
||||
module('urllib')
|
||||
module('xmlrpc')
|
||||
|
||||
# To add the multitude of standard Python string encodings.
|
||||
module('encodings', 'encodings.*')
|
||||
|
||||
class models(package):
|
||||
# The standard models package. This is the contents of the
|
||||
# "models" directory that ships with Panda; it includes a few
|
||||
# default fonts, and some silly little sample models like smiley
|
||||
# and teapot.
|
||||
config(display_name = "Standard models")
|
||||
|
||||
# We assign it the same version as the panda3d package. This
|
||||
# would be assigned by default if we had a requirement on panda3d,
|
||||
# but there's no real reason to declare that requirement.
|
||||
config(version = PandaSystem.getPackageVersionString())
|
||||
|
||||
# Look for cmss12.egg on the model-path. Wherever this is found,
|
||||
# we assume this is the models directory.
|
||||
pathname = getModelPath().findFile('cmss12.egg')
|
||||
if pathname:
|
||||
dir(pathname.getDirname(), newDir = 'models')
|
||||
|
||||
# Some people are used to loading the models without models/ prefix.
|
||||
file('models.prc', extract = True, text = "model-path $MODELS_ROOT/models")
|
||||
|
||||
|
||||
class fmod(package):
|
||||
# This package includes the FMod audio library. This is
|
||||
# full-featured and robust, but it is closed-source and the
|
||||
# licensing is cost-free only for non-commercial products.
|
||||
|
||||
config(display_name = "FMod audio library")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3fmod_audio.dll', required = True)
|
||||
|
||||
file('fmod.prc', extract = True, text = """
|
||||
plugin-path $FMOD_ROOT
|
||||
audio-library-name p3fmod_audio
|
||||
""")
|
||||
|
||||
class openal(package):
|
||||
# This package includes the OpenAL audio libraries. This is free
|
||||
# in both senses, but there are some technical issues, especially
|
||||
# on certain platforms.
|
||||
|
||||
config(display_name = "OpenAL audio library")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3openal_audio.dll', required = True)
|
||||
file('OpenAL32.dll')
|
||||
file('soft_oal.dll')
|
||||
file('wrap_oal.dll')
|
||||
|
||||
file('openal.prc', extract = True, text = """
|
||||
plugin-path $OPENAL_ROOT
|
||||
audio-library-name p3openal_audio
|
||||
""")
|
||||
|
||||
class audio(package):
|
||||
# This package includes the best audio library for the given
|
||||
# platform, assuming a non-commercial application.
|
||||
require('panda3d')
|
||||
|
||||
# OpenAL seems to work pretty well universally now.
|
||||
require('openal')
|
||||
|
||||
class ffmpeg(package):
|
||||
# This package includes the ffmpeg libraries and integration.
|
||||
# It's a bit big, and is restricted by patents and the restrictive
|
||||
# GPLv3 license, which is why it is included as a separate package.
|
||||
|
||||
config(display_name = "FFMpeg multimedia decoder")
|
||||
require('panda3d')
|
||||
|
||||
file('libp3ffmpeg.dll', required = True)
|
||||
|
||||
file('ffmpeg.prc', extract = True, text = """
|
||||
plugin-path $FFMPEG_ROOT
|
||||
load-audio-type * p3ffmpeg
|
||||
load-video-type * p3ffmpeg
|
||||
""")
|
||||
|
||||
class egg(package):
|
||||
# This package contains the code for reading and operating on egg
|
||||
# files and other model files. Since the Packager automatically
|
||||
# converts egg files to bam files, this is not needed for most
|
||||
# Panda3D applications.
|
||||
|
||||
config(display_name = "Panda3D egg loader")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.egg', required = True)
|
||||
file('libpandaegg.dll', required = True)
|
||||
file('libp3ptloader.dll', required = True)
|
||||
|
||||
file('egg.prc', extract = True, text = """
|
||||
plugin-path $EGG_ROOT
|
||||
load-file-type egg pandaegg
|
||||
load-file-type p3ptloader
|
||||
|
||||
# These are excerpted from the default Confauto.prc file.
|
||||
egg-object-type-portal <Scalar> portal { 1 }
|
||||
egg-object-type-polylight <Scalar> polylight { 1 }
|
||||
egg-object-type-seq24 <Switch> { 1 } <Scalar> fps { 24 }
|
||||
egg-object-type-seq12 <Switch> { 1 } <Scalar> fps { 12 }
|
||||
egg-object-type-indexed <Scalar> indexed { 1 }
|
||||
egg-object-type-seq10 <Switch> { 1 } <Scalar> fps { 10 }
|
||||
egg-object-type-seq8 <Switch> { 1 } <Scalar> fps { 8 }
|
||||
egg-object-type-seq6 <Switch> { 1 } <Scalar> fps { 6 }
|
||||
egg-object-type-seq4 <Switch> { 1 } <Scalar> fps { 4 }
|
||||
egg-object-type-seq2 <Switch> { 1 } <Scalar> fps { 2 }
|
||||
|
||||
egg-object-type-binary <Scalar> alpha { binary }
|
||||
egg-object-type-dual <Scalar> alpha { dual }
|
||||
egg-object-type-glass <Scalar> alpha { blend_no_occlude }
|
||||
|
||||
egg-object-type-model <Model> { 1 }
|
||||
egg-object-type-dcs <DCS> { 1 }
|
||||
egg-object-type-notouch <DCS> { no_touch }
|
||||
|
||||
egg-object-type-barrier <Collide> { Polyset descend }
|
||||
egg-object-type-sphere <Collide> { Sphere descend }
|
||||
egg-object-type-invsphere <Collide> { InvSphere descend }
|
||||
egg-object-type-tube <Collide> { Tube descend }
|
||||
egg-object-type-trigger <Collide> { Polyset descend intangible }
|
||||
egg-object-type-trigger-sphere <Collide> { Sphere descend intangible }
|
||||
egg-object-type-floor <Collide> { Polyset descend level }
|
||||
egg-object-type-dupefloor <Collide> { Polyset keep descend level }
|
||||
egg-object-type-bubble <Collide> { Sphere keep descend }
|
||||
egg-object-type-ghost <Scalar> collide-mask { 0 }
|
||||
egg-object-type-glow <Scalar> blend { add }
|
||||
egg-object-type-direct-widget <Scalar> collide-mask { 0x80000000 } <Collide> { Polyset descend }
|
||||
""")
|
||||
|
||||
class ode(package):
|
||||
# This package contains the code for the ODE integration.
|
||||
# As not every application uses the ODE layers, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D Open Dynamics Engine integration")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.ode', required = True)
|
||||
file('libpandaode.dll', required = True)
|
||||
|
||||
class bullet(package):
|
||||
# This package contains the code for the Bullet integration.
|
||||
# As not every application uses the Bullet layers, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D Bullet integration")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.bullet', required = True)
|
||||
file('libpandabullet.dll', required = True)
|
||||
|
||||
class physx(package):
|
||||
# This package contains the code for the NVIDIA PhysX integration.
|
||||
# As not every application uses the NVIDIA PhysX layers, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D PhysX integration")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.physx', required = True)
|
||||
file('libpandaphysx.dll', required = True)
|
||||
file('physxcudart_20.dll')
|
||||
file('PhysXDevice.dll')
|
||||
|
||||
class ai(package):
|
||||
# This package contains the PandAI library for pathfinding
|
||||
# and seek/flee behaviors. As some games may not need this
|
||||
# functionality, it is provided as separate package to save
|
||||
# on package download size.
|
||||
|
||||
config(display_name = "Panda3D AI modules")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.ai', required = True)
|
||||
file('libpandaai.dll', required = True)
|
||||
|
||||
class vision(package):
|
||||
# This package contains the code for webcam support, augmented
|
||||
# reality and computer vision. As many games will not need this
|
||||
# functionality, it is provided as separate package to save
|
||||
# on package download size.
|
||||
|
||||
config(display_name = "Panda3D vision modules")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.vision', required = True)
|
||||
file('libp3vision.dll', required = True)
|
||||
|
||||
class rocket(package):
|
||||
# This package contains the code for libRocket integration.
|
||||
# As not every application uses libRocket GUI, and to cut down
|
||||
# the download size, it is provided as separate package.
|
||||
|
||||
config(display_name = "Panda3D libRocket support")
|
||||
require('panda3d')
|
||||
|
||||
file('rocket.py', extract = True, text = """
|
||||
from _rocketcore import *
|
||||
try:
|
||||
from _rocketcontrols import *
|
||||
except ImportError:
|
||||
pass
|
||||
""")
|
||||
|
||||
module('panda3d.rocket', '_rocketcore', required = True)
|
||||
module('_rocketcontrols')
|
||||
file('libp3rocket.dll', required = True)
|
||||
|
||||
class vrpn(package):
|
||||
# This package contains the code for VRPN integration.
|
||||
# Most applications don't use this, which is why it's separate.
|
||||
|
||||
config(display_name = "Panda3D VRPN support")
|
||||
require('panda3d')
|
||||
|
||||
module('panda3d.vrpn', required = True)
|
||||
file('libp3vrpn.dll', required = True)
|
||||
|
||||
|
||||
class packp3d(p3d):
|
||||
# This application is a command-line convenience for building a p3d
|
||||
# application out of a directory hierarchy on disk. We build it here
|
||||
# into its own p3d application, to allow end-users to easily build p3d
|
||||
# applications using the appropriate version of Python and Panda for
|
||||
# the targeted runtime.
|
||||
|
||||
config(display_name = "Panda3D Application Packer",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d', 'egg')
|
||||
|
||||
mainModule('direct.p3d.packp3d')
|
||||
|
||||
file('packp3d.prc', extract = True, text = "preload-textures false")
|
||||
|
||||
|
||||
class ppackage(p3d):
|
||||
# As above, a packaging utility. This is the fully-general ppackage
|
||||
# utility, which reads pdef files (like this one!) and creates one or
|
||||
# more packages or p3d applications.
|
||||
|
||||
config(display_name = "Panda3D General Package Utility",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d', 'egg')
|
||||
|
||||
mainModule('direct.p3d.ppackage')
|
||||
|
||||
file('ppackage.prc', extract = True, text = "preload-textures false")
|
||||
|
||||
|
||||
class ppatcher(p3d):
|
||||
# A handy utility to go along with ppackage. This builds
|
||||
# patchfiles as needed in the directory structure created by
|
||||
# ppackage.
|
||||
|
||||
config(display_name = "Panda3D Patch Maker",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d')
|
||||
|
||||
mainModule('direct.p3d.ppatcher')
|
||||
|
||||
|
||||
class pmerge(p3d):
|
||||
# Another handy utility to go along with ppackage. This
|
||||
# merges multiple directory structures as created by
|
||||
# ppackage into a single one.
|
||||
|
||||
config(display_name = "Panda3D Package Merger",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d')
|
||||
|
||||
mainModule('direct.p3d.pmerge')
|
||||
|
||||
|
||||
class pdeploy(p3d):
|
||||
# This utility can distribute a game in the form of
|
||||
# a standalone executable or a graphical installer.
|
||||
|
||||
config(display_name = "Panda3D Deployment Tool",
|
||||
hidden = True, platform_specific = False,
|
||||
keep_user_env = True)
|
||||
require('panda3d', 'morepy')
|
||||
|
||||
mainModule('direct.p3d.pdeploy')
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This command will help you to distribute your Panda application,
|
||||
consisting of a .p3d package, into a standalone executable, graphical
|
||||
installer or an HTML webpage. It will attempt to create packages
|
||||
for every platform, if possible.
|
||||
Note that pdeploy requires an internet connection to run.
|
||||
|
||||
When used with the 'installer' option, it is strongly advisable
|
||||
to specify most if not all of the descriptive information that can
|
||||
be passed on the command-line.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] app.p3d standalone|installer|html
|
||||
|
||||
Modes:
|
||||
|
||||
standalone
|
||||
A standalone executable will be created that embeds the given
|
||||
p3d file. The resulting executable will require an
|
||||
internet connection in order to run properly.
|
||||
|
||||
installer
|
||||
In this mode, installable packages will be created for as many
|
||||
platforms as possible. To create Windows installers on
|
||||
non-Windows platforms, you need to have the "makensis" utility
|
||||
on your system PATH environment variable.
|
||||
|
||||
html
|
||||
An HTML webpage will be generated that can be used to view
|
||||
the provided p3d file in a browser.
|
||||
|
||||
Options:
|
||||
|
||||
-n your_app
|
||||
Short, lowercase name of the application or game. May only
|
||||
contain alphanumeric characters, underscore or dash. This
|
||||
name will also define the output file(s) of the process.
|
||||
If omitted, the basename of the p3d file is used.
|
||||
|
||||
-N "Your Application"
|
||||
Full name of the application or game. This is the
|
||||
name that will be displayed to the end-user.
|
||||
The 'display_name' config is used by default. If it
|
||||
is missing, the short name is used.
|
||||
|
||||
-v version_number
|
||||
This should define the version number of your application
|
||||
or game. In some deploy modes, this argument is required.
|
||||
This should only contain alphanumeric characters, dots and
|
||||
dashes, as otherwise the result of the deployment may be
|
||||
invalid on some platforms.
|
||||
|
||||
-o output_dir
|
||||
Indicates the directory where the output will be stored.
|
||||
Within this directory, subdirectories will be created
|
||||
for every platform, unless -t is provided.
|
||||
If omitted, the current working directory is assumed.
|
||||
|
||||
-t token=value
|
||||
Defines a web token or parameter to pass to the application.
|
||||
Use this to configure how the application will be run.
|
||||
You can pass as many -t options as you need. Some examples of
|
||||
useful token names are width, height, log_basename, auto_start,
|
||||
hidden and console_environment.
|
||||
|
||||
-P platform
|
||||
If this option is provided, it should specify a comma-
|
||||
separated list of platforms that the p3d package will be
|
||||
deployed for. If omitted, it will be built for all platforms.
|
||||
This option may be specified multiple times.
|
||||
Examples of valid platforms are win_i386, linux_amd64 and osx_ppc.
|
||||
|
||||
-c
|
||||
If this option is provided, any -P options are ignored and
|
||||
the p3d package is only deployed for the current platform.
|
||||
Furthermore, no per-platform subdirectories will be created
|
||||
inside the output dirctory.
|
||||
|
||||
-s
|
||||
This option only has effect in 'installer' mode. If it is
|
||||
provided, the resulting installers will be fully self-contained,
|
||||
will not require an internet connection to run, and start up
|
||||
much faster. Note that pdeploy will also take a very long time
|
||||
to finish when -s is provided.
|
||||
If it is omitted, pdeploy will finish much quicker, and the
|
||||
resulting installers will be smaller, but they will require
|
||||
an internet connection for the first run, and the load time
|
||||
will be considerably longer.
|
||||
|
||||
-l "License Name"
|
||||
Specifies the name of the software license that the game
|
||||
or application is licensed under.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-L licensefile.txt
|
||||
This should point to a file that contains the full text
|
||||
describing the software license that the game or application
|
||||
is licensed under.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-O
|
||||
Specify this option when generating a graphical installer to omit
|
||||
the default checkboxes for "run this program" and "install a
|
||||
desktop shortcut" on completion.
|
||||
|
||||
-a com.your_company
|
||||
Short identifier of the author of the application. The default
|
||||
is "org.panda3d", but you will most likely want to change
|
||||
it to your own name or that of your organization or company.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-A "Your Company"
|
||||
Full name of the author of the application. The default is
|
||||
determined from the GECOS information of the current user if
|
||||
available; if not, your username is used.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-e "you@your_company.com"
|
||||
E-mail address of the maintainer of the application. The default
|
||||
is username@hostname.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-i "path/icon32.png" -i "path/icon48.png" -i "path/icon128.png"
|
||||
This option should be repeated several times with different square
|
||||
image sizes. These images will then be combined to form an icon
|
||||
file that will be used to represent the installed application.
|
||||
To support all platforms, it is recommended to supply images of
|
||||
the sizes 16x16, 32x32, 48x48, 128x128, 256x256, and 512x512.
|
||||
The larger icon sizes can safely be omitted if you cannot
|
||||
provide images in that resolution.
|
||||
It is recommended to use .png images for correct transparency.
|
||||
If no images are provided, no icon will be generated.
|
||||
Only relevant when generating a graphical installer.
|
||||
|
||||
-h
|
||||
Display this help
|
||||
|
||||
"""
|
||||
|
||||
DEPLOY_MODES = ["standalone", "installer", "html"]
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
from direct.p3d.DeploymentTools import Standalone, Installer, Icon
|
||||
from panda3d.core import Filename, PandaSystem
|
||||
|
||||
def usage(code, msg = ''):
|
||||
if not msg:
|
||||
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
shortname = ""
|
||||
fullname = ""
|
||||
version = ""
|
||||
outputDir = Filename("./")
|
||||
tokens = {}
|
||||
platforms = []
|
||||
currentPlatform = False
|
||||
licensename = ""
|
||||
licensefile = Filename()
|
||||
authorid = ""
|
||||
authorname = ""
|
||||
authoremail = ""
|
||||
iconFiles = []
|
||||
includeRequires = False
|
||||
omitDefaultCheckboxes = False
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'n:N:v:o:t:P:csOl:L:a:A:e:i:h')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg or 'Invalid option')
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '-n':
|
||||
shortname = arg.strip()
|
||||
elif opt == '-N':
|
||||
fullname = arg.strip()
|
||||
elif opt == '-v':
|
||||
version = arg.strip()
|
||||
elif opt == '-o':
|
||||
outputDir = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-t':
|
||||
token = arg.strip().split("=", 1)
|
||||
tokens[token[0]] = token[1]
|
||||
elif opt == '-P':
|
||||
platforms.append(arg)
|
||||
elif opt == '-c':
|
||||
currentPlatform = True
|
||||
elif opt == '-s':
|
||||
includeRequires = True
|
||||
elif opt == '-O':
|
||||
omitDefaultCheckboxes = True
|
||||
elif opt == '-l':
|
||||
licensename = arg.strip()
|
||||
elif opt == '-L':
|
||||
licensefile = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-a':
|
||||
authorid = arg.strip()
|
||||
elif opt == '-A':
|
||||
authorname = arg.strip()
|
||||
elif opt == '-e':
|
||||
authoremail = arg.strip()
|
||||
elif opt == '-i':
|
||||
iconFiles.append(Filename.fromOsSpecific(arg))
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
msg = 'illegal option: ' + flag
|
||||
print(msg)
|
||||
sys.exit(1, msg)
|
||||
|
||||
if not args or len(args) != 2:
|
||||
msg = 'Wrong number of arguments, received %s, expected 2' % (
|
||||
len(args or []))
|
||||
usage(1, msg)
|
||||
|
||||
appFilename = Filename.fromOsSpecific(args[0])
|
||||
if appFilename.getExtension().lower() != 'p3d':
|
||||
print('Application filename must end in ".p3d".')
|
||||
sys.exit(1)
|
||||
deploy_mode = args[1].lower()
|
||||
|
||||
if not appFilename.exists():
|
||||
print('Application filename does not exist!')
|
||||
sys.exit(1)
|
||||
|
||||
if shortname == '':
|
||||
shortname = appFilename.getBasenameWoExtension()
|
||||
|
||||
if shortname.lower() != shortname or ' ' in shortname:
|
||||
print('\nProvided short name should be lowercase, and may not contain spaces!\n')
|
||||
|
||||
if version == '' and deploy_mode == 'installer':
|
||||
print('\nA version number is required in "installer" mode.\n')
|
||||
sys.exit(1)
|
||||
|
||||
if not outputDir:
|
||||
print('\nYou must name the output directory with the -o parameter.\n')
|
||||
sys.exit(1)
|
||||
if not outputDir.exists():
|
||||
print('\nThe specified output directory does not exist!\n')
|
||||
sys.exit(1)
|
||||
elif not outputDir.isDirectory():
|
||||
print('\nThe specified output directory is a file!\n')
|
||||
sys.exit(1)
|
||||
|
||||
if deploy_mode == 'standalone':
|
||||
s = Standalone(appFilename, tokens)
|
||||
s.basename = shortname
|
||||
|
||||
if currentPlatform:
|
||||
platform = PandaSystem.getPlatform()
|
||||
if platform.startswith("win"):
|
||||
s.build(Filename(outputDir, shortname + ".exe"), platform)
|
||||
else:
|
||||
s.build(Filename(outputDir, shortname), platform)
|
||||
elif len(platforms) == 0:
|
||||
s.buildAll(outputDir)
|
||||
else:
|
||||
for platform in platforms:
|
||||
if platform.startswith("win"):
|
||||
s.build(Filename(outputDir, platform + "/" + shortname + ".exe"), platform)
|
||||
else:
|
||||
s.build(Filename(outputDir, platform + "/" + shortname), platform)
|
||||
|
||||
elif deploy_mode == 'installer':
|
||||
if includeRequires:
|
||||
tokens["verify_contents"] = "never"
|
||||
i = Installer(appFilename, shortname, fullname, version, tokens = tokens)
|
||||
i.includeRequires = includeRequires
|
||||
if omitDefaultCheckboxes:
|
||||
i.offerRun = False
|
||||
i.offerDesktopShortcut = False
|
||||
i.licensename = licensename
|
||||
i.licensefile = licensefile
|
||||
if authorid:
|
||||
i.authorid = authorid
|
||||
if authorname:
|
||||
i.authorname = authorname
|
||||
if authoremail:
|
||||
i.authoremail = authoremail
|
||||
if not authorname or not authoremail or not authorid:
|
||||
print("Using author \"%s\" <%s> with ID %s" % \
|
||||
(i.authorname, i.authoremail, i.authorid))
|
||||
|
||||
# Add the supplied icon images
|
||||
if len(iconFiles) > 0:
|
||||
failed = False
|
||||
i.icon = Icon()
|
||||
for iconFile in iconFiles:
|
||||
if not i.icon.addImage(iconFile):
|
||||
print('\nFailed to add icon image "%s"!\n' % iconFile)
|
||||
failed = True
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
|
||||
# Now build for the requested platforms.
|
||||
if currentPlatform:
|
||||
platform = PandaSystem.getPlatform()
|
||||
if platform.startswith("win"):
|
||||
i.build(outputDir, platform)
|
||||
else:
|
||||
i.build(outputDir, platform)
|
||||
elif len(platforms) == 0:
|
||||
i.buildAll(outputDir)
|
||||
else:
|
||||
for platform in platforms:
|
||||
output = Filename(outputDir, platform + "/")
|
||||
output.makeDir()
|
||||
i.build(output, platform)
|
||||
|
||||
del i
|
||||
|
||||
elif deploy_mode == 'html':
|
||||
w, h = tokens.get("width", 800), tokens.get("height", 600)
|
||||
if "data" not in tokens:
|
||||
tokens["data"] = appFilename.getBasename()
|
||||
|
||||
print("Creating %s.html..." % shortname)
|
||||
html = open(Filename(outputDir, shortname + ".html").toOsSpecific(), "w")
|
||||
html.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||
html.write("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||
html.write(" <head>\n")
|
||||
html.write(" <title>%s</title>\n" % fullname)
|
||||
html.write(" <meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\" />\n")
|
||||
if authorname:
|
||||
html.write(" <meta name=\"Author\" content=\"%s\" />\n" % authorname.replace('"', '\\"'))
|
||||
html.write(" </head>\n")
|
||||
html.write(" <body>\n")
|
||||
html.write(" <object")
|
||||
for key, value in tokens.items():
|
||||
html.write(" %s=\"%s\"" % (key, value.replace('"', '\\"')))
|
||||
if "width" not in tokens:
|
||||
html.write(" width=\"%s\"" % w)
|
||||
if "height" not in tokens:
|
||||
html.write(" height=\"%s\"" % h)
|
||||
html.write(" type=\"application/x-panda3d\">")
|
||||
html.write(" <object width=\"%s\" height=\"%s\" classid=\"CLSID:924B4927-D3BA-41EA-9F7E-8A89194AB3AC\">\n" % (w, h))
|
||||
for key, value in tokens.items():
|
||||
html.write(" <param name=\"%s\" value=\"%s\" />\n" % (key, value.replace('"', '\\"')))
|
||||
html.write(" </object>\n")
|
||||
html.write(" </object>\n")
|
||||
html.write(" </body>\n")
|
||||
html.write("</html>\n")
|
||||
html.close()
|
||||
else:
|
||||
usage(1, 'Invalid deployment mode!')
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This script can be used to merge together the contents of two or more
|
||||
separately-built stage directories, built independently via ppackage,
|
||||
or via Packager.py. This script also verifies the hash, file size,
|
||||
and timestamp values in the stage directory as it runs, so it can be
|
||||
run on a single standalone directory just to perform this validation.
|
||||
|
||||
This script is actually a wrapper around Panda's PackageMerger.py.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] [inputdir1 .. inputdirN]
|
||||
|
||||
Parameters:
|
||||
|
||||
inputdir1 .. inputdirN
|
||||
Specify the full path to the input directories you wish to merge.
|
||||
These are the directories specified by -i on the previous
|
||||
invocations of ppackage. The order is mostly unimportant.
|
||||
|
||||
Options:
|
||||
|
||||
-i install_dir
|
||||
The full path to the final install directory. This may also
|
||||
contain some pre-existing contents; if so, it is merged with all
|
||||
of the input directories as well. The contents of this directory
|
||||
are checked for self-consistency with regards to hashes and
|
||||
timestamps.
|
||||
|
||||
-p packageName[,packageName...]
|
||||
Specifies one or more particular packages by name that are to be
|
||||
included from the input directories. Any packages not in this
|
||||
list are left unchanged in the install directory, even if there
|
||||
is a newer version in one of the input directories. If no
|
||||
packages are named, all packages are involved. This option may
|
||||
be repeated.
|
||||
|
||||
-h
|
||||
Display this help
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import os
|
||||
|
||||
from direct.p3d import PackageMerger
|
||||
from panda3d.core import Filename
|
||||
|
||||
def usage(code, msg = ''):
|
||||
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'i:p:h')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg)
|
||||
|
||||
installDir = None
|
||||
packageNames = []
|
||||
for opt, arg in opts:
|
||||
if opt == '-i':
|
||||
installDir = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-p':
|
||||
packageNames += arg.split(',')
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
print('illegal option: ' + arg)
|
||||
sys.exit(1)
|
||||
|
||||
if not packageNames:
|
||||
# No package names means allow all packages.
|
||||
packageNames = None
|
||||
|
||||
inputDirs = []
|
||||
for arg in args:
|
||||
inputDirs.append(Filename.fromOsSpecific(arg))
|
||||
|
||||
# It's now legal to have no input files if you only want to verify
|
||||
# timestamps and hashes.
|
||||
## if not inputDirs:
|
||||
## print "no input directories specified."
|
||||
## sys.exit(1)
|
||||
|
||||
try:
|
||||
pm = PackageMerger.PackageMerger(installDir)
|
||||
for dir in inputDirs:
|
||||
pm.merge(dir, packageNames = packageNames)
|
||||
pm.close()
|
||||
|
||||
except PackageMerger.PackageMergerError:
|
||||
# Just print the error message and exit gracefully.
|
||||
inst = sys.exc_info()[1]
|
||||
print(inst.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
@@ -1,267 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
This script can be used to produce a Panda3D downloadable "package",
|
||||
which may contain arbitrary files--for instance, Python code, bam
|
||||
files, and/or compiled DLL's--and which may be downloaded by
|
||||
application code to extend an application at runtime.
|
||||
|
||||
In addition to packages, this script can also be used to build
|
||||
standalone p3d applications, that is, packaged Python code in a p3d
|
||||
file, for execution by the Panda3D plugin or runtime. (But also see
|
||||
packp3d, which is designed to be a simpler interface for building
|
||||
applications.)
|
||||
|
||||
This command will build p3d files that reference Panda3D %(version)s,
|
||||
from host %(host)s .
|
||||
|
||||
This script is actually a wrapper around Panda's Packager.py, which
|
||||
can be invoked directly by Python code that requires a programmatic
|
||||
interface to building packages.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] package.pdef [packageName1 .. packageNameN]
|
||||
|
||||
Parameters:
|
||||
|
||||
package.pdef
|
||||
The config file that describes the contents of the package file(s)
|
||||
to be built, in excruciating detail. See the Panda3D manual for
|
||||
the syntax of this file.
|
||||
|
||||
packageName1 .. packageNameN
|
||||
Specify the names of the package(s) you wish to build out of the
|
||||
package.pdef file. This allows you to build only a subset of the
|
||||
packages defined in this file. If you omit these parameters, all
|
||||
packages are built, and packages that cannot be built are silently
|
||||
ignored.
|
||||
|
||||
Options:
|
||||
|
||||
-i install_dir
|
||||
The full path to a local directory to copy the
|
||||
ready-to-be-published files into. This directory structure may
|
||||
contain multiple different packages from multiple different
|
||||
invocations of this script. It is the user's responsibility to
|
||||
copy this directory structure to a server, which will have the
|
||||
URL specified by the packager.setHost() call appearing within the
|
||||
pdef file.
|
||||
|
||||
-p
|
||||
Automatically build patches against previous versions after
|
||||
generating the results. Patches are difference files that users
|
||||
can download when updating a package, in lieu of redownloading
|
||||
the whole package; this happens automatically if patches are
|
||||
present. You should generally build patches when you are
|
||||
committing to a final, public-facing release. Patches are
|
||||
usually a good idea, but generating a patch for each internal
|
||||
test build may needlessly generate a lot of small, inefficient
|
||||
patch files instead of a few larger ones. You can also generate
|
||||
patches after the fact, by running ppatcher on the install
|
||||
directory.
|
||||
|
||||
-s search_dir
|
||||
Additional directories to search for previously-built packages.
|
||||
This option may be repeated as necessary. These directories may
|
||||
also be specified with the pdef-path Config.prc variable.
|
||||
|
||||
-S file.crt[,chain.crt[,file.key[,\"password\"]]]
|
||||
Signs any resulting p3d file(s) with the indicated certificate.
|
||||
You may specify the signing certificate, the optional
|
||||
authorization chain, and the private key in three different
|
||||
files, or they may all be combined in the first file. If the
|
||||
private key is encrypted, the password will be required to
|
||||
decrypt it.
|
||||
|
||||
-D
|
||||
Sets the allow_python_dev flag in any applications built with
|
||||
this command. This enables additional runtime debug operations,
|
||||
particularly the -i option to the panda3d command, which enables
|
||||
a live Python prompt within the application's environment.
|
||||
Setting this flag may be useful to develop an application
|
||||
initially, but should not be set on an application intended for
|
||||
deployment.
|
||||
|
||||
-N
|
||||
If this option is set, Packager will not try to compile any Python
|
||||
files to .pyc or .pyo, instead storing the original source files.
|
||||
|
||||
-u
|
||||
On the Mac OSX platform, this means that Panda was built with
|
||||
universal binaries, and the package should be built that way as
|
||||
well (that is, a version of the the package should be created for
|
||||
each supported architecture). On other platforms, this option
|
||||
does nothing. This is therefore safe to apply in all cases, if
|
||||
you wish to take advantage of universal binaries. This is
|
||||
equivalent to "-P osx_i386 -P osx_amd64" on Mac platforms.
|
||||
|
||||
-P platform
|
||||
Specify the platform to masquerade as. The default is whatever
|
||||
platform Panda has been built for. You can use this on Mac OSX
|
||||
in order to build packages for an alternate architecture if you
|
||||
have built Panda with universal binaries; you may repeat this
|
||||
option for each architecture you wish to support. For other
|
||||
platforms, it is probably a mistake to set this. However, see
|
||||
the option -u.
|
||||
|
||||
-R sysroot
|
||||
Specify the sysroot that these files were compiled against. This
|
||||
will shadow the system shared libraries, so that alternate
|
||||
versions are used instead of the system versions. If any program
|
||||
references a library, say /usr/lib/libfoo.so, and
|
||||
/sysroot/usr/lib/libfoo.so exists instead, that file will be used
|
||||
instead of the system library. This is particularly useful for
|
||||
cross-compilation. At the moment, this is supported only on OSX.
|
||||
|
||||
-H
|
||||
Treats a packager.setHost() call in the pdef file as if it were
|
||||
merely a call to packager.addHost(). This allows producing a
|
||||
package for an alternate host than its normally configured host,
|
||||
which is sometimes useful in development.
|
||||
|
||||
-a suffix
|
||||
Appends the given suffix to the p3d filename, before the extension.
|
||||
This is useful when the same packages are compiled several times
|
||||
but using different settings, and you want to mark those packages
|
||||
as such. This only applies to .p3d packages, not to other types
|
||||
of packages!
|
||||
|
||||
-v
|
||||
Emit a warning for any file not recognized by the dir() command
|
||||
(indicating there may be a need for addExtensions(...)).
|
||||
|
||||
-h
|
||||
Display this help
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import os
|
||||
|
||||
from direct.p3d import Packager
|
||||
from panda3d.core import *
|
||||
|
||||
def usage(code, msg = ''):
|
||||
sys.stderr.write(usageText % {
|
||||
'version' : PandaSystem.getPackageVersionString(),
|
||||
'host' : PandaSystem.getPackageHostUrl(),
|
||||
'prog' : os.path.split(sys.argv[0])[1]
|
||||
})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
installDir = None
|
||||
buildPatches = False
|
||||
installSearch = []
|
||||
signParams = []
|
||||
allowPythonDev = False
|
||||
storePythonSource = False
|
||||
universalBinaries = False
|
||||
systemRoot = None
|
||||
ignoreSetHost = False
|
||||
verbosePrint = False
|
||||
p3dSuffix = ''
|
||||
platforms = []
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DNuP:R:Ha:hv')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg)
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '-i':
|
||||
installDir = Filename.fromOsSpecific(arg)
|
||||
elif opt == '-p':
|
||||
buildPatches = True
|
||||
elif opt == '-s':
|
||||
installSearch.append(Filename.fromOsSpecific(arg))
|
||||
elif opt == '-S':
|
||||
tokens = arg.split(',')
|
||||
while len(tokens) < 4:
|
||||
tokens.append('')
|
||||
certificate, chain, pkey, password = tokens[:4]
|
||||
signParams.append((Filename.fromOsSpecific(certificate),
|
||||
Filename.fromOsSpecific(chain),
|
||||
Filename.fromOsSpecific(pkey),
|
||||
Filename.fromOsSpecific(password)))
|
||||
elif opt == '-D':
|
||||
allowPythonDev = True
|
||||
elif opt == '-N':
|
||||
storePythonSource = True
|
||||
elif opt == '-u':
|
||||
universalBinaries = True
|
||||
elif opt == '-P':
|
||||
platforms.append(arg)
|
||||
elif opt == '-R':
|
||||
systemRoot = arg
|
||||
elif opt == '-H':
|
||||
ignoreSetHost = True
|
||||
elif opt == '-a':
|
||||
p3dSuffix = arg
|
||||
|
||||
elif opt == '-v':
|
||||
verbosePrint = True
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
print('illegal option: ' + arg)
|
||||
sys.exit(1)
|
||||
|
||||
if not args:
|
||||
usage(0)
|
||||
|
||||
packageDef = Filename.fromOsSpecific(args[0])
|
||||
packageNames = None
|
||||
if len(args) > 1:
|
||||
packageNames = args[1:]
|
||||
|
||||
# Add the directory containing the pdef file itself to sys.path, to
|
||||
# help the Packager locate modules where a pathname isn't specified.
|
||||
dirname = packageDef.getDirname()
|
||||
if dirname:
|
||||
sys.path.append(Filename(dirname).toOsSpecific())
|
||||
else:
|
||||
sys.path.append('.')
|
||||
|
||||
if universalBinaries:
|
||||
if platforms:
|
||||
print('\nYou may not specify both -u and -P.\n')
|
||||
sys.exit(1)
|
||||
if PandaSystem.getPlatform().startswith('osx_'):
|
||||
platforms = ['osx_i386', 'osx_amd64']
|
||||
|
||||
if not platforms:
|
||||
platforms = [PandaSystem.getPlatform()]
|
||||
|
||||
for platform in platforms:
|
||||
packager = Packager.Packager(platform = platform)
|
||||
packager.installDir = installDir
|
||||
packager.installSearch = installSearch + packager.installSearch
|
||||
if installDir is not None:
|
||||
packager.installSearch = [installDir] + packager.installSearch
|
||||
packager.signParams = signParams
|
||||
packager.allowPythonDev = allowPythonDev
|
||||
packager.storePythonSource = storePythonSource
|
||||
packager.systemRoot = systemRoot
|
||||
packager.ignoreSetHost = ignoreSetHost
|
||||
packager.verbosePrint = verbosePrint
|
||||
packager.p3dSuffix = p3dSuffix
|
||||
|
||||
try:
|
||||
packager.setup()
|
||||
packages = packager.readPackageDef(packageDef, packageNames = packageNames)
|
||||
packager.close()
|
||||
if buildPatches:
|
||||
packager.buildPatches(packages)
|
||||
|
||||
except Packager.PackagerError:
|
||||
# Just print the error message and exit gracefully.
|
||||
inst = sys.exc_info()[1]
|
||||
print(inst.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
@@ -1,101 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
usageText = """
|
||||
|
||||
This script generates the patches required to support incremental
|
||||
download of Panda3D packages. It can be run as a post-process on a
|
||||
directory hierarchy created by ppackage; it will examine the directory
|
||||
hierarchy, and create any patches that appear to be missing.
|
||||
|
||||
You may run ppackage on the same directory hierarchy as many times as
|
||||
you like, without creating patches. You may then download and test
|
||||
the resulting files--users connecting to the tree without fresh
|
||||
patches will be forced to download the entire file, instead of making
|
||||
an incremental download, but the entire process will work otherwise.
|
||||
When you are satisfied that all of the files are ready to be released,
|
||||
you may run ppackage on the directory hierarchy to generate the
|
||||
required patches.
|
||||
|
||||
Generating the patches just before final release is a good idea to
|
||||
limit the number of trivially small patches that are created. Each
|
||||
time this script is run, a patch is created from the previous version,
|
||||
and these patches daisy-chain together to define a complete update
|
||||
sequence. If you run this script on internal releases, you will
|
||||
generate a long chain of small patches that your users must download;
|
||||
this is pointless if there is no possibility of anyone having
|
||||
downloaded one of the intervening versions.
|
||||
|
||||
You can also generate patches with the -p option to ppackage, but that
|
||||
only generates patches for the specific packages built by that
|
||||
invocation of ppackage. If you use the ppatcher script instead, it
|
||||
will generate patches for all packages (or the set of packages that
|
||||
you name specifically).
|
||||
|
||||
This script is actually a wrapper around Panda's PatchMaker.py.
|
||||
|
||||
Usage:
|
||||
|
||||
%(prog)s [opts] [packageName1 .. packageNameN]
|
||||
|
||||
Parameters:
|
||||
|
||||
packageName1 .. packageNameN
|
||||
Specify the names of the package(s) you wish to generate patches
|
||||
for. This allows you to build patches for only a subset of the
|
||||
packages found in the tree. If you omit these parameters, patches
|
||||
are built for all packages that require them.
|
||||
|
||||
Options:
|
||||
|
||||
-i install_dir
|
||||
The full path to the install directory. This should be the same
|
||||
directory named by the -i parameter to ppackage.
|
||||
|
||||
-h
|
||||
Display this help
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import os
|
||||
|
||||
from direct.p3d.PatchMaker import PatchMaker
|
||||
from panda3d.core import Filename
|
||||
|
||||
def usage(code, msg = ''):
|
||||
sys.stderr.write(usageText % {'prog' : os.path.split(sys.argv[0])[1]})
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'i:h')
|
||||
except getopt.error as msg:
|
||||
usage(1, msg)
|
||||
|
||||
installDir = None
|
||||
for opt, arg in opts:
|
||||
if opt == '-i':
|
||||
installDir = Filename.fromOsSpecific(arg)
|
||||
|
||||
elif opt == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
print('illegal option: ' + arg)
|
||||
sys.exit(1)
|
||||
|
||||
packageNames = args
|
||||
|
||||
if not installDir:
|
||||
installDir = Filename('install')
|
||||
|
||||
if not packageNames:
|
||||
# "None" means all packages.
|
||||
packageNames = None
|
||||
|
||||
pm = PatchMaker(installDir)
|
||||
pm.buildPatches(packageNames = packageNames)
|
||||
|
||||
# An explicit call to exit() is required to exit the program, when
|
||||
# this module is packaged in a p3d file.
|
||||
sys.exit(0)
|
||||
@@ -1,79 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
"""
|
||||
|
||||
This tool will invoke the AppRunner to execute a packaged p3d
|
||||
application. It requires that that the current Panda3D and Python
|
||||
versions match the version expected by the application.
|
||||
|
||||
Normally, you do not need to use this tool; instead, use the provided
|
||||
standalone panda3d executable to invoke any p3d application. Using
|
||||
panda3d will guarantee that the correct versions of Panda3D and Python
|
||||
are used to run the application. However, there may be occasions when
|
||||
it is useful to use this tool to run the application with the current
|
||||
build instead of with its advertised version requirements.
|
||||
|
||||
Usage:
|
||||
|
||||
runp3d.py app.p3d [args]
|
||||
|
||||
The command-line arguments following the application name are passed
|
||||
into the application unchanged.
|
||||
|
||||
See pack3d.p3d for an application that generates these p3d files.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
from .AppRunner import AppRunner, ArgumentError
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from panda3d.core import Filename
|
||||
|
||||
def parseSysArgs():
|
||||
""" Handles sys.argv, if there are any local arguments, and
|
||||
returns a new argv suitable for passing into the
|
||||
application. """
|
||||
|
||||
# We prefix a "+" sign, following the GNU convention, to tell
|
||||
# getopt not to parse options following the first non-option
|
||||
# parameter.
|
||||
opts, args = getopt.getopt(sys.argv[1:], '+h')
|
||||
|
||||
for option, value in opts:
|
||||
if option == '-h':
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
if not args or not args[0]:
|
||||
raise ArgumentError("No Panda app specified. Use:\nrunp3d.py app.p3d")
|
||||
|
||||
arg0 = args[0]
|
||||
p3dFilename = Filename.fromOsSpecific(arg0)
|
||||
if p3dFilename.exists():
|
||||
p3dFilename.makeAbsolute()
|
||||
arg0 = p3dFilename.toOsSpecific()
|
||||
|
||||
return [arg0] + args[1:]
|
||||
|
||||
def runPackedApp(pathname):
|
||||
runner = AppRunner()
|
||||
runner.gotWindow = True
|
||||
try:
|
||||
runner.setP3DFilename(pathname, tokens = [], argv = [],
|
||||
instanceId = 0, interactiveConsole = False)
|
||||
except ArgumentError as e:
|
||||
print(e.args[0])
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = AppRunner()
|
||||
runner.gotWindow = True
|
||||
try:
|
||||
argv = parseSysArgs()
|
||||
runner.setP3DFilename(argv[0], tokens = [], argv = argv,
|
||||
instanceId = 0, interactiveConsole = False)
|
||||
except ArgumentError as e:
|
||||
print(e.args[0])
|
||||
sys.exit(1)
|
||||
taskMgr.run()
|
||||
@@ -1,167 +0,0 @@
|
||||
# This file defines a number of standard "packages" that correspond to
|
||||
# a Panda3D distribution. These packages are built by passing this
|
||||
# file to the ppackage utility, either as a packaged application, or
|
||||
# as the module direct.p3d.ppackage.
|
||||
|
||||
# The packages in this file define the thirdparty
|
||||
# packages that could be used by a .p3d archive.
|
||||
|
||||
# When needed, these packages are downloaded by the core API, from the
|
||||
# host URL specified in a given p3d file, and not from any hardcoded
|
||||
# URL. Thus, any custom version of Panda3D may be hosted on any
|
||||
# server in the world, and any version of the plugin can download it.
|
||||
|
||||
# Also see panda3d.pdef.
|
||||
|
||||
from panda3d.core import Filename
|
||||
import sys
|
||||
|
||||
class wx(package):
|
||||
config(display_name = "wxPython GUI Toolkit")
|
||||
#config(gui_app = True)
|
||||
require('panda3d')
|
||||
|
||||
module('direct.showbase.WxGlobal')
|
||||
module('direct.wxwidgets')
|
||||
module('direct.wxwidgets.*')
|
||||
module('wx', required = True)
|
||||
module('wx.*')
|
||||
|
||||
class tk(package):
|
||||
config(display_name = "Tk GUI Toolkit")
|
||||
#config(gui_app = True)
|
||||
require('panda3d')
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
module('tkinter', '_tkinter', required = True)
|
||||
module('tkinter.colorchooser', 'tkinter.commondialog',
|
||||
'tkinter.constants', 'tkinter.dialog', 'tkinter.dnd',
|
||||
'tkinter.filedialog', 'tkinter.font', 'tkinter.messagebox',
|
||||
'tkinter.scrolledtext', 'tkinter.simpledialog', 'tkinter.tix',
|
||||
'tkinter.ttk')
|
||||
else:
|
||||
module('Tkinter', '_tkinter', required = True)
|
||||
module('Tkconstants', 'Tkdnd', 'Tix', 'ScrolledText', 'turtle',
|
||||
'tkColorChooser', 'tkCommonDialog', 'tkFileDialog',
|
||||
'tkFont', 'tkMessageBox', 'tkSimpleDialog')
|
||||
|
||||
module('direct.showbase.TkGlobal',
|
||||
'direct.tkpanels',
|
||||
'direct.tkwidgets')
|
||||
|
||||
try:
|
||||
if sys.version_info >= (3, 0):
|
||||
from tkinter import Tcl
|
||||
else:
|
||||
from Tkinter import Tcl
|
||||
tcl = Tcl()
|
||||
dir = Filename.fromOsSpecific(tcl.eval("info library"))
|
||||
ver = tcl.eval("info tclversion")
|
||||
|
||||
file(Filename(dir, "*.tcl"), newDir = 'lib/tcl' + ver, extract = True)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import Pmw
|
||||
except ImportError:
|
||||
Pmw = None
|
||||
|
||||
if Pmw is not None:
|
||||
Pmw = sys.modules['_Pmw']
|
||||
sys.modules['Pmw'] = Pmw
|
||||
sys.modules['Pmw'].__name__ = 'Pmw'
|
||||
module('Pmw', 'Pmw.Pmw_1_3.lib.*')
|
||||
path = Filename.fromOsSpecific(Pmw.__path__[0])
|
||||
file(Filename(path, 'Pmw_1_3/lib/Pmw.def'), newDir = 'Pmw/Pmw_1_3/lib')
|
||||
|
||||
class sqlite(package):
|
||||
config(display_name = "SQLite Interface")
|
||||
require('panda3d')
|
||||
|
||||
module('sqlite', 'sqlite3')
|
||||
|
||||
class numpy(package):
|
||||
config(display_name = "NumPy")
|
||||
require('panda3d')
|
||||
|
||||
module('numpy', required = True)
|
||||
|
||||
class pygame(package):
|
||||
config(display_name = "PyGame")
|
||||
require('panda3d', 'numpy')
|
||||
|
||||
module('pygame', required = True)
|
||||
module('pygame.*', 'pygame._view')
|
||||
|
||||
class twisted(package):
|
||||
config(display_name = "Twisted")
|
||||
require('panda3d')
|
||||
|
||||
module('twisted', 'twisted._version', required = True)
|
||||
module('twisted.application', 'twisted.conch', 'twisted.cred',
|
||||
'twisted.enterprise', 'twisted.internet', 'twisted.lore',
|
||||
'twisted.mail', 'twisted.manhole', 'twisted.names',
|
||||
'twisted.news', 'twisted.pair', 'twisted.persisted',
|
||||
'twisted.plugin', 'twisted.plugins', 'twisted.python',
|
||||
'twisted.runner', 'twisted.scripts', 'twisted.spread',
|
||||
'twisted.tap', 'twisted.trial', 'twisted.vfs',
|
||||
'twisted.web', 'twisted.web2', 'twisted.words')
|
||||
|
||||
module('twisted.*', 'twisted.*.*')
|
||||
|
||||
class pil(package):
|
||||
config(display_name = "Python Imaging Library")
|
||||
require('panda3d')
|
||||
|
||||
module('Image', required = True)
|
||||
module('PIL', 'ArgImagePlugin', 'BdfFontFile',
|
||||
'BmpImagePlugin', 'BufrStubImagePlugin', 'ContainerIO',
|
||||
'CurImagePlugin', 'DcxImagePlugin', 'EpsImagePlugin',
|
||||
'ExifTags', 'FitsStubImagePlugin', 'FliImagePlugin',
|
||||
'FontFile', 'FpxImagePlugin', 'GbrImagePlugin', 'GdImageFile',
|
||||
'GifImagePlugin', 'GimpGradientFile', 'GimpPaletteFile',
|
||||
'GribStubImagePlugin', 'Hdf5StubImagePlugin',
|
||||
'IcnsImagePlugin', 'IcoImagePlugin', 'ImImagePlugin',
|
||||
'ImageChops', 'ImageCms', 'ImageColor', 'ImageDraw',
|
||||
'ImageDraw2', 'ImageEnhance', 'ImageFile', 'ImageFileIO',
|
||||
'ImageFilter', 'ImageFont', 'ImageGL', 'ImageGrab',
|
||||
'ImageMath', 'ImageMode', 'ImageOps', 'ImagePalette',
|
||||
'ImagePath', 'ImageQt', 'ImageSequence', 'ImageShow',
|
||||
'ImageStat', 'ImageTk', 'ImageTransform', 'ImageWin',
|
||||
'ImtImagePlugin', 'IptcImagePlugin', 'JpegImagePlugin',
|
||||
'McIdasImagePlugin', 'MicImagePlugin', 'MpegImagePlugin',
|
||||
'MspImagePlugin', 'OleFileIO', 'PSDraw', 'PaletteFile',
|
||||
'PalmImagePlugin', 'PcdImagePlugin', 'PcfFontFile',
|
||||
'PcxImagePlugin', 'PdfImagePlugin', 'PixarImagePlugin',
|
||||
'PngImagePlugin', 'PpmImagePlugin', 'PsdImagePlugin',
|
||||
'SgiImagePlugin', 'SpiderImagePlugin', 'SunImagePlugin',
|
||||
'TarIO', 'TgaImagePlugin', 'TiffImagePlugin', 'TiffTags',
|
||||
'WalImageFile', 'WmfImagePlugin', 'XVThumbImagePlugin',
|
||||
'XbmImagePlugin', 'XpmImagePlugin', '_imaging',
|
||||
'_imagingmath', '_imagingtk')
|
||||
|
||||
class pyopengl(package):
|
||||
config(display_name = "PyOpenGL")
|
||||
require('panda3d', 'numpy')
|
||||
|
||||
module('OpenGL', 'OpenGL.GL', required = True)
|
||||
module('OpenGL.GLU', 'OpenGL.GLUT', 'OpenGL.GLE', 'OpenGL.GLX')
|
||||
|
||||
class httplib2(package):
|
||||
config(display_name = "httplib2", platform_specific = False)
|
||||
require('panda3d')
|
||||
|
||||
module('httplib2', required = True)
|
||||
|
||||
class box2d(package):
|
||||
config(display_name = "Box2D")
|
||||
require('panda3d')
|
||||
|
||||
module('Box2D', required = True)
|
||||
|
||||
class pyglet(package):
|
||||
config(display_name = "pyglet", platform_specific = False)
|
||||
require('panda3d', 'morepy')
|
||||
|
||||
module('pyglet', required = True)
|
||||
@@ -1,351 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file binaryXml.cxx
|
||||
* @author drose
|
||||
* @date 2009-07-13
|
||||
*/
|
||||
|
||||
#include "binaryXml.h"
|
||||
#include "p3d_lock.h"
|
||||
#include <sstream>
|
||||
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::ostringstream;
|
||||
using std::string;
|
||||
|
||||
static const bool debug_xml_output = false;
|
||||
|
||||
static LOCK xml_lock;
|
||||
static bool xml_lock_initialized = false;
|
||||
|
||||
#define DO_BINARY_XML 1
|
||||
|
||||
enum NodeType {
|
||||
NT_unknown,
|
||||
NT_document,
|
||||
NT_element,
|
||||
NT_text,
|
||||
};
|
||||
|
||||
// This typedef defines a 32-bit unsigned integer. It's used for passing
|
||||
// values through the binary XML stream.
|
||||
typedef unsigned int xml_uint32;
|
||||
|
||||
// These are both prime numbers, though I don't know if that really matters.
|
||||
// Mainly, they're big random numbers.
|
||||
static const xml_uint32 length_nonce1 = 812311453;
|
||||
static const xml_uint32 length_nonce2 = 612811373;
|
||||
|
||||
/**
|
||||
* Should be called before spawning any threads to ensure the lock is
|
||||
* initialized.
|
||||
*/
|
||||
void
|
||||
init_xml() {
|
||||
if (!xml_lock_initialized) {
|
||||
INIT_LOCK(xml_lock);
|
||||
xml_lock_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively writes a node and all of its children to the given stream.
|
||||
*/
|
||||
static void
|
||||
write_xml_node(ostream &out, TiXmlNode *xnode) {
|
||||
const string &value = xnode->ValueStr();
|
||||
xml_uint32 value_length = value.length();
|
||||
xml_uint32 value_proof = (value_length + length_nonce1) * length_nonce2;
|
||||
|
||||
// We write out not only value_length, but the same value again hashed by
|
||||
// length_nonce1 and 2 (and truncated back to xml_uint32), just to prove to
|
||||
// the reader that we're still on the same page. We do this only on the top
|
||||
// node; we don't bother for the nested nodes.
|
||||
out.write((char *)&value_length, sizeof(value_length));
|
||||
out.write((char *)&value_proof, sizeof(value_proof));
|
||||
out.write(value.data(), value_length);
|
||||
|
||||
// Now write out the node type.
|
||||
NodeType type = NT_element;
|
||||
if (xnode->ToDocument() != nullptr) {
|
||||
type = NT_document;
|
||||
} else if (xnode->ToElement() != nullptr) {
|
||||
type = NT_element;
|
||||
} else if (xnode->ToText() != nullptr) {
|
||||
type = NT_text;
|
||||
} else {
|
||||
type = NT_unknown;
|
||||
}
|
||||
|
||||
out.put((char)type);
|
||||
// We don't bother to write any further data for the unknown types.
|
||||
if (type == NT_unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == NT_element) {
|
||||
// Write the element attributes.
|
||||
TiXmlElement *xelement = xnode->ToElement();
|
||||
assert(xelement != nullptr);
|
||||
const TiXmlAttribute *xattrib = xelement->FirstAttribute();
|
||||
|
||||
while (xattrib != nullptr) {
|
||||
// We have an attribute.
|
||||
out.put((char)true);
|
||||
|
||||
string name = xattrib->Name();
|
||||
xml_uint32 name_length = name.length();
|
||||
out.write((char *)&name_length, sizeof(name_length));
|
||||
out.write(name.data(), name_length);
|
||||
|
||||
const string &value = xattrib->ValueStr();
|
||||
xml_uint32 value_length = value.length();
|
||||
out.write((char *)&value_length, sizeof(value_length));
|
||||
out.write(value.data(), value_length);
|
||||
|
||||
xattrib = xattrib->Next();
|
||||
}
|
||||
|
||||
// The end of the attributes list.
|
||||
out.put((char)false);
|
||||
}
|
||||
|
||||
// Now write all of the children.
|
||||
TiXmlNode *xchild = xnode->FirstChild();
|
||||
while (xchild != nullptr) {
|
||||
// We have a child.
|
||||
out.put((char)true);
|
||||
write_xml_node(out, xchild);
|
||||
xchild = xchild->NextSibling();
|
||||
}
|
||||
|
||||
// The end of the children list.
|
||||
out.put((char)false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively reads a node and all of its children to the given stream.
|
||||
* Returns the newly-allocated node. The caller is responsible for eventually
|
||||
* deleting the return value. Returns NULL on error.
|
||||
*/
|
||||
static TiXmlNode *
|
||||
read_xml_node(istream &in, char *&buffer, xml_uint32 &buffer_length,
|
||||
ostream &logfile) {
|
||||
xml_uint32 value_length;
|
||||
in.read((char *)&value_length, sizeof(value_length));
|
||||
if (in.gcount() != sizeof(value_length)) {
|
||||
return nullptr;
|
||||
}
|
||||
xml_uint32 value_proof_expect = (value_length + length_nonce1) * length_nonce2;
|
||||
xml_uint32 value_proof;
|
||||
in.read((char *)&value_proof, sizeof(value_proof));
|
||||
if (in.gcount() != sizeof(value_proof)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (value_proof != value_proof_expect) {
|
||||
// Hey, we ran into garbage: the proof value didn't match our expected
|
||||
// proof value.
|
||||
logfile << "Garbage on XML stream!\n";
|
||||
|
||||
// Print out the garbage; maybe it will help the developer figure out
|
||||
// where it came from.
|
||||
logfile << "Begin garbage:\n";
|
||||
ostringstream strm;
|
||||
strm.write((char *)&value_length, sizeof(value_length));
|
||||
strm.write((char *)&value_proof, sizeof(value_proof));
|
||||
logfile << strm.str();
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
int ch = in.get();
|
||||
if (ch != EOF) {
|
||||
logfile.put(ch);
|
||||
}
|
||||
}
|
||||
logfile << "\n";
|
||||
logfile << "End garbage.\n";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (value_length > buffer_length) {
|
||||
delete[] buffer;
|
||||
buffer_length = value_length;
|
||||
buffer = new char[buffer_length];
|
||||
}
|
||||
|
||||
in.read(buffer, value_length);
|
||||
string value(buffer, value_length);
|
||||
|
||||
// Read the node type.
|
||||
NodeType type = (NodeType)in.get();
|
||||
if (type == NT_unknown) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TiXmlNode *xnode = nullptr;
|
||||
if (type == NT_element) {
|
||||
xnode = new TiXmlElement(value);
|
||||
} else if (type == NT_document) {
|
||||
xnode = new TiXmlDocument;
|
||||
} else if (type == NT_text) {
|
||||
xnode = new TiXmlText(value);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (type == NT_element) {
|
||||
// Read the element attributes.
|
||||
TiXmlElement *xelement = xnode->ToElement();
|
||||
assert(xelement != nullptr);
|
||||
bool got_attrib = (bool)(in.get() != 0);
|
||||
|
||||
while (got_attrib && in && !in.eof()) {
|
||||
// We have an attribute.
|
||||
xml_uint32 name_length;
|
||||
in.read((char *)&name_length, sizeof(name_length));
|
||||
if (in.gcount() != sizeof(name_length)) {
|
||||
delete xnode;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (name_length > buffer_length) {
|
||||
delete[] buffer;
|
||||
buffer_length = name_length;
|
||||
buffer = new char[buffer_length];
|
||||
}
|
||||
|
||||
in.read(buffer, name_length);
|
||||
string name(buffer, name_length);
|
||||
|
||||
xml_uint32 value_length;
|
||||
in.read((char *)&value_length, sizeof(value_length));
|
||||
if (in.gcount() != sizeof(value_length)) {
|
||||
delete xnode;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (value_length > buffer_length) {
|
||||
delete[] buffer;
|
||||
buffer_length = value_length;
|
||||
buffer = new char[buffer_length];
|
||||
}
|
||||
|
||||
in.read(buffer, value_length);
|
||||
string value(buffer, value_length);
|
||||
|
||||
xelement->SetAttribute(name, value);
|
||||
|
||||
got_attrib = (bool)(in.get() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Now read all of the children.
|
||||
bool got_child = (bool)(in.get() != 0);
|
||||
|
||||
while (got_child && in && !in.eof()) {
|
||||
// We have a child.
|
||||
TiXmlNode *xchild = read_xml_node(in, buffer, buffer_length, logfile);
|
||||
if (xchild != nullptr) {
|
||||
xnode->LinkEndChild(xchild);
|
||||
}
|
||||
|
||||
got_child = (bool)(in.get() != 0);
|
||||
}
|
||||
|
||||
return xnode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Writes the indicated TinyXml document to the given stream.
|
||||
*/
|
||||
void
|
||||
write_xml(ostream &out, TiXmlDocument *doc, ostream &logfile) {
|
||||
assert(xml_lock_initialized);
|
||||
ACQUIRE_LOCK(xml_lock);
|
||||
|
||||
#ifdef DO_BINARY_XML
|
||||
// Binary write.
|
||||
write_xml_node(out, doc);
|
||||
|
||||
#else
|
||||
// Formatted ASCII write.
|
||||
|
||||
// We need a declaration to write it safely.
|
||||
TiXmlDeclaration decl("1.0", "utf-8", "");
|
||||
doc->InsertBeforeChild(doc->FirstChild(), decl);
|
||||
|
||||
out << *doc;
|
||||
#endif
|
||||
|
||||
out << flush;
|
||||
|
||||
if (debug_xml_output) {
|
||||
// Write via ostringstream, so it all goes in one operation, to help out
|
||||
// the interleaving from multiple threads.
|
||||
ostringstream logout;
|
||||
logout << "sent: " << *doc << "\n";
|
||||
logfile << logout.str() << flush;
|
||||
}
|
||||
|
||||
RELEASE_LOCK(xml_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a TinyXml document from the given stream, and returns it. If the
|
||||
* document is not yet available, blocks until it is, or until there is an
|
||||
* error condition on the input.
|
||||
*
|
||||
* The return value is NULL if there is an error, or the newly-allocated
|
||||
* document if it is successfully read. If not NULL, the document has been
|
||||
* allocated with new, and should be eventually freed by the caller with
|
||||
* delete.
|
||||
*/
|
||||
TiXmlDocument *
|
||||
read_xml(istream &in, ostream &logfile) {
|
||||
// We don't acquire xml_lock while reading. We can't, because our XML
|
||||
// readers are all designed to block until data is available, and they can't
|
||||
// block while holding the lock.
|
||||
|
||||
// Fortunately, there should be only one reader at a time, so a lock isn't
|
||||
// really needed here.
|
||||
|
||||
#if DO_BINARY_XML
|
||||
// binary read.
|
||||
xml_uint32 buffer_length = 128;
|
||||
char *buffer = new char[buffer_length];
|
||||
TiXmlNode *xnode = read_xml_node(in, buffer, buffer_length, logfile);
|
||||
delete[] buffer;
|
||||
if (xnode == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TiXmlDocument *doc = xnode->ToDocument();
|
||||
assert(doc != nullptr);
|
||||
|
||||
#else
|
||||
// standard ASCII read.
|
||||
TiXmlDocument *doc = new TiXmlDocument;
|
||||
in >> *doc;
|
||||
if (in.fail() || in.eof()) {
|
||||
delete doc;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (debug_xml_output) {
|
||||
// Write via ostringstream, so it all goes in one operation, to help out
|
||||
// the interleaving from multiple threads.
|
||||
ostringstream logout;
|
||||
logout << "received: " << *doc << "\n";
|
||||
logfile << logout.str() << flush;
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file binaryXml.h
|
||||
* @author drose
|
||||
* @date 2009-07-13
|
||||
*/
|
||||
|
||||
#ifndef BINARYXML_H
|
||||
#define BINARYXML_H
|
||||
|
||||
#include "get_tinyxml.h"
|
||||
#include "handleStream.h"
|
||||
#include <iostream>
|
||||
|
||||
// A pair of functions to input and output the TinyXml constructs on the
|
||||
// indicated streams. We could, of course, use the TinyXml output operators,
|
||||
// but this is a smidge more efficient and gives us more control.
|
||||
|
||||
void init_xml();
|
||||
void write_xml(std::ostream &out, TiXmlDocument *doc, std::ostream &logfile);
|
||||
TiXmlDocument *read_xml(std::istream &in, std::ostream &logfile);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file fhandle.h
|
||||
* @author drose
|
||||
* @date 2009-08-29
|
||||
*/
|
||||
|
||||
#ifndef FHANDLE_H
|
||||
#define FHANDLE_H
|
||||
|
||||
// This header file simply defines the FHandle type, which is used to pass
|
||||
// around a handle to an open file object.
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
typedef HANDLE FHandle;
|
||||
static const HANDLE invalid_fhandle = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
// On POSIX, we use a file descriptor as a "handle".
|
||||
typedef int FHandle;
|
||||
static const int invalid_fhandle = -1;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file fileSpec.I
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the relative path to this file on disk, within the package root
|
||||
* directory.
|
||||
*/
|
||||
inline const std::string &FileSpec::
|
||||
get_filename() const {
|
||||
return _filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the relative path to this file on disk, within the package root
|
||||
* directory.
|
||||
*/
|
||||
inline void FileSpec::
|
||||
set_filename(const std::string &filename) {
|
||||
_filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to this file on disk.
|
||||
*/
|
||||
inline std::string FileSpec::
|
||||
get_pathname(const std::string &package_dir) const {
|
||||
return package_dir + "/" + _filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected size of this file on disk, in bytes.
|
||||
*/
|
||||
inline size_t FileSpec::
|
||||
get_size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected last-modify timestamp of this file on disk.
|
||||
*/
|
||||
inline time_t FileSpec::
|
||||
get_timestamp() const {
|
||||
return _timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we have successfully read a hash value, false otherwise.
|
||||
*/
|
||||
inline bool FileSpec::
|
||||
has_hash() const {
|
||||
return _got_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* After a call to quick_verify() or full_verify(), this method *may* return a
|
||||
* pointer to a FileSpec that represents the actual data read on disk, or it
|
||||
* may return NULL. If this returns a non-NULL value, you may use it to
|
||||
* extract the md5 hash of the existing file, thus saving the effort of
|
||||
* performing the hash twice.
|
||||
*/
|
||||
inline const FileSpec *FileSpec::
|
||||
get_actual_file() const {
|
||||
return _actual_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer value corresponding to the indicated hex digit.
|
||||
* Returns -1 if it is not a hex digit.
|
||||
*/
|
||||
inline int FileSpec::
|
||||
decode_hexdigit(char c) {
|
||||
if (isdigit(c)) {
|
||||
return c - '0';
|
||||
}
|
||||
c = tolower(c);
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hex digit corresponding to the indicated integer value.
|
||||
*/
|
||||
inline char FileSpec::
|
||||
encode_hexdigit(int c) {
|
||||
if (c >= 10) {
|
||||
return c - 10 + 'a';
|
||||
}
|
||||
return c + '0';
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file fileSpec.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
#include "fileSpec.h"
|
||||
#include "wstring_encode.h"
|
||||
#include <openssl/md5.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <sys/utime.h>
|
||||
#include <direct.h>
|
||||
#define utimbuf _utimbuf
|
||||
|
||||
#else
|
||||
#include <utime.h>
|
||||
|
||||
#endif
|
||||
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
FileSpec::
|
||||
FileSpec() {
|
||||
_size = 0;
|
||||
_timestamp = 0;
|
||||
memset(_hash, 0, hash_size);
|
||||
_got_hash = false;
|
||||
_actual_file = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
FileSpec::
|
||||
FileSpec(const FileSpec ©) :
|
||||
_filename(copy._filename),
|
||||
_size(copy._size),
|
||||
_timestamp(copy._timestamp),
|
||||
_got_hash(copy._got_hash)
|
||||
{
|
||||
memcpy(_hash, copy._hash, hash_size);
|
||||
_actual_file = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void FileSpec::
|
||||
operator = (const FileSpec ©) {
|
||||
_filename = copy._filename;
|
||||
_size = copy._size;
|
||||
_timestamp = copy._timestamp;
|
||||
memcpy(_hash, copy._hash, hash_size);
|
||||
_got_hash = copy._got_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
FileSpec::
|
||||
~FileSpec() {
|
||||
if (_actual_file != nullptr) {
|
||||
delete _actual_file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the data from the indicated XML file.
|
||||
*/
|
||||
void FileSpec::
|
||||
load_xml(TiXmlElement *xelement) {
|
||||
const char *filename = xelement->Attribute("filename");
|
||||
if (filename != nullptr) {
|
||||
_filename = filename;
|
||||
}
|
||||
|
||||
const char *size = xelement->Attribute("size");
|
||||
if (size != nullptr) {
|
||||
char *endptr;
|
||||
_size = strtoul(size, &endptr, 10);
|
||||
}
|
||||
|
||||
const char *timestamp = xelement->Attribute("timestamp");
|
||||
if (timestamp != nullptr) {
|
||||
char *endptr;
|
||||
_timestamp = strtoul(timestamp, &endptr, 10);
|
||||
}
|
||||
|
||||
_got_hash = false;
|
||||
const char *hash = xelement->Attribute("hash");
|
||||
if (hash != nullptr && strlen(hash) == (hash_size * 2)) {
|
||||
// Decode the hex hash string.
|
||||
_got_hash = decode_hex(_hash, hash, hash_size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the data to the indicated XML file.
|
||||
*/
|
||||
void FileSpec::
|
||||
store_xml(TiXmlElement *xelement) {
|
||||
if (!_filename.empty()) {
|
||||
xelement->SetAttribute("filename", _filename);
|
||||
}
|
||||
if (_size != 0) {
|
||||
xelement->SetAttribute("size", _size);
|
||||
}
|
||||
if (_timestamp != 0) {
|
||||
xelement->SetAttribute("timestamp", (int)_timestamp);
|
||||
}
|
||||
if (_got_hash) {
|
||||
char hash[hash_size * 2 + 1];
|
||||
encode_hex(hash, _hash, hash_size);
|
||||
hash[hash_size * 2] = '\0';
|
||||
xelement->SetAttribute("hash", hash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a quick test to ensure the file has not been modified. This test
|
||||
* is vulnerable to people maliciously attempting to fool the program (by
|
||||
* setting datestamps etc.).
|
||||
*
|
||||
* Returns true if it is intact, false if it needs to be redownloaded.
|
||||
*/
|
||||
bool FileSpec::
|
||||
quick_verify(const string &package_dir) {
|
||||
return quick_verify_pathname(get_pathname(package_dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Works like quick_verify(), above, with an explicit pathname. Useful for
|
||||
* verifying the copy of a file in a temporary location.
|
||||
*/
|
||||
bool FileSpec::
|
||||
quick_verify_pathname(const string &pathname) {
|
||||
if (_actual_file != nullptr) {
|
||||
delete _actual_file;
|
||||
_actual_file = nullptr;
|
||||
}
|
||||
|
||||
int result = 1;
|
||||
#ifdef _WIN32
|
||||
struct _stat st;
|
||||
wstring pathname_w;
|
||||
if (string_to_wstring(pathname_w, pathname)) {
|
||||
result = _wstat(pathname_w.c_str(), &st);
|
||||
}
|
||||
#else // _WIN32
|
||||
struct stat st;
|
||||
result = stat(pathname.c_str(), &st);
|
||||
#endif // _WIN32
|
||||
|
||||
if (result != 0) {
|
||||
// cerr << "file not found: " << _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st.st_size != _size) {
|
||||
// If the size is wrong, the file fails. cerr << "size wrong: " <<
|
||||
// _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st.st_mtime == _timestamp) {
|
||||
// If the size is right and the timestamp is right, the file passes. cerr
|
||||
// << "file ok: " << _filename << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
// cerr << "modification time wrong: " << _filename << "\n";
|
||||
|
||||
// If the size is right but the timestamp is wrong, the file soft-fails. We
|
||||
// follow this up with a hash check.
|
||||
if (!priv_check_hash(pathname, &st)) {
|
||||
// Hard fail, the hash is wrong. cerr << "hash check wrong: " <<
|
||||
// _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// cerr << "hash check ok: " << _filename << "\n";
|
||||
|
||||
// The hash is OK after all. Change the file's timestamp back to what we
|
||||
// expect it to be, so we can quick-verify it successfully next time.
|
||||
utimbuf utb;
|
||||
utb.actime = st.st_atime;
|
||||
utb.modtime = _timestamp;
|
||||
|
||||
#ifdef _WIN32
|
||||
_wutime(pathname_w.c_str(), &utb);
|
||||
#else // _WIN32
|
||||
utime(pathname.c_str(), &utb);
|
||||
#endif // _WIN32
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a more thorough test to ensure the file has not been modified.
|
||||
* This test is less vulnerable to malicious attacks, since it reads and
|
||||
* verifies the entire file.
|
||||
*
|
||||
* Returns true if it is intact, false if it needs to be redownloaded.
|
||||
*/
|
||||
bool FileSpec::
|
||||
full_verify(const string &package_dir) {
|
||||
if (_actual_file != nullptr) {
|
||||
delete _actual_file;
|
||||
_actual_file = nullptr;
|
||||
}
|
||||
|
||||
string pathname = get_pathname(package_dir);
|
||||
int result = 1;
|
||||
#ifdef _WIN32
|
||||
struct _stat st;
|
||||
wstring pathname_w;
|
||||
if (string_to_wstring(pathname_w, pathname)) {
|
||||
result = _wstat(pathname_w.c_str(), &st);
|
||||
}
|
||||
#else // _WIN32
|
||||
struct stat st;
|
||||
result = stat(pathname.c_str(), &st);
|
||||
#endif // _WIN32
|
||||
|
||||
if (result != 0) {
|
||||
// cerr << "file not found: " << _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st.st_size != _size) {
|
||||
// If the size is wrong, the file fails. cerr << "size wrong: " <<
|
||||
// _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!priv_check_hash(pathname, &st)) {
|
||||
// Hard fail, the hash is wrong. cerr << "hash check wrong: " <<
|
||||
// _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// cerr << "hash check ok: " << _filename << "\n";
|
||||
|
||||
// The hash is OK. If the timestamp is wrong, change it back to what we
|
||||
// expect it to be, so we can quick-verify it successfully next time.
|
||||
|
||||
if (st.st_mtime != _timestamp) {
|
||||
utimbuf utb;
|
||||
utb.actime = st.st_atime;
|
||||
utb.modtime = _timestamp;
|
||||
#ifdef _WIN32
|
||||
_wutime(pathname_w.c_str(), &utb);
|
||||
#else // _WIN32
|
||||
utime(pathname.c_str(), &utb);
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FileSpec that represents the actual data read on disk. This will
|
||||
* read the disk to determine the data if necessary.
|
||||
*/
|
||||
const FileSpec *FileSpec::
|
||||
force_get_actual_file(const string &pathname) {
|
||||
if (_actual_file == nullptr) {
|
||||
#ifdef _WIN32
|
||||
struct _stat st;
|
||||
wstring pathname_w;
|
||||
if (string_to_wstring(pathname_w, pathname)) {
|
||||
_wstat(pathname_w.c_str(), &st);
|
||||
}
|
||||
#else // _WIN32
|
||||
struct stat st;
|
||||
stat(pathname.c_str(), &st);
|
||||
#endif // _WIN32
|
||||
|
||||
priv_check_hash(pathname, &st);
|
||||
}
|
||||
|
||||
return _actual_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the file has the expected md5 hash, false otherwise.
|
||||
*/
|
||||
bool FileSpec::
|
||||
check_hash(const string &pathname) const {
|
||||
FileSpec other;
|
||||
if (!other.read_hash(pathname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (memcmp(_hash, other._hash, hash_size) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hash from the indicated pathname and stores it within the
|
||||
* FileSpec.
|
||||
*/
|
||||
bool FileSpec::
|
||||
read_hash(const string &pathname) {
|
||||
memset(_hash, 0, hash_size);
|
||||
_got_hash = false;
|
||||
|
||||
ifstream stream;
|
||||
#ifdef _WIN32
|
||||
wstring pathname_w;
|
||||
if (string_to_wstring(pathname_w, pathname)) {
|
||||
stream.open(pathname_w.c_str(), std::ios::in | std::ios::binary);
|
||||
}
|
||||
#else // _WIN32
|
||||
stream.open(pathname.c_str(), std::ios::in | std::ios::binary);
|
||||
#endif // _WIN32
|
||||
|
||||
if (!stream) {
|
||||
// cerr << "unable to read " << pathname << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
MD5_CTX ctx;
|
||||
MD5_Init(&ctx);
|
||||
|
||||
static const int buffer_size = 4096;
|
||||
char buffer[buffer_size];
|
||||
|
||||
stream.read(buffer, buffer_size);
|
||||
size_t count = stream.gcount();
|
||||
while (count != 0) {
|
||||
MD5_Update(&ctx, buffer, count);
|
||||
stream.read(buffer, buffer_size);
|
||||
count = stream.gcount();
|
||||
}
|
||||
|
||||
MD5_Final(_hash, &ctx);
|
||||
_got_hash = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the hash from the next 16 bytes on the indicated istream, in the same
|
||||
* unusual order observed by Panda's HashVal::read_stream() method.
|
||||
*/
|
||||
bool FileSpec::
|
||||
read_hash_stream(istream &in) {
|
||||
for (int i = 0; i < hash_size; i += 4) {
|
||||
unsigned int a = in.get();
|
||||
unsigned int b = in.get();
|
||||
unsigned int c = in.get();
|
||||
unsigned int d = in.get();
|
||||
_hash[i + 0] = d;
|
||||
_hash[i + 1] = c;
|
||||
_hash[i + 2] = b;
|
||||
_hash[i + 3] = a;
|
||||
}
|
||||
|
||||
return !in.fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns < 0 if this hash sorts before the other hash, > 0 if it sorts
|
||||
* after, 0 if they are the same.
|
||||
*/
|
||||
int FileSpec::
|
||||
compare_hash(const FileSpec &other) const {
|
||||
return memcmp(_hash, other._hash, hash_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the data in the FileSpec.
|
||||
*/
|
||||
void FileSpec::
|
||||
write(ostream &out) const {
|
||||
out << "filename: " << _filename << ", " << _size << " bytes, "
|
||||
<< asctime(localtime(&_timestamp));
|
||||
// asctime includes a newline.
|
||||
out << "hash: ";
|
||||
stream_hex(out, _hash, hash_size);
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes just the hash code.
|
||||
*/
|
||||
void FileSpec::
|
||||
output_hash(ostream &out) const {
|
||||
stream_hex(out, _hash, hash_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the file has the expected md5 hash, false otherwise.
|
||||
* Updates _actual_file with the data read from disk, including the hash, for
|
||||
* future reference.
|
||||
*
|
||||
* The parameter stp is a pointer to a stat structure. It's declared as a
|
||||
* void * to get around issues with the nonstandard declaration of this
|
||||
* structure in Windows.
|
||||
*/
|
||||
bool FileSpec::
|
||||
priv_check_hash(const string &pathname, void *stp) {
|
||||
const struct stat &st = *(const struct stat *)stp;
|
||||
assert(_actual_file == nullptr);
|
||||
_actual_file = new FileSpec;
|
||||
_actual_file->_filename = pathname;
|
||||
_actual_file->_size = st.st_size;
|
||||
_actual_file->_timestamp = st.st_mtime;
|
||||
|
||||
if (!_actual_file->read_hash(pathname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (memcmp(_hash, _actual_file->_hash, hash_size) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the hex string in source into the character array in dest. dest
|
||||
* must have has least size bytes; source must have size * 2 bytes.
|
||||
*
|
||||
* Returns true on success, false if there was a non-hex digit in the string.
|
||||
*/
|
||||
bool FileSpec::
|
||||
decode_hex(unsigned char *dest, const char *source, size_t size) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
int high = decode_hexdigit(source[i * 2]);
|
||||
int low = decode_hexdigit(source[i * 2 + 1]);
|
||||
if (high < 0 || low < 0) {
|
||||
return false;
|
||||
}
|
||||
dest[i] = (high << 4) | low;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a character array into a hex string for output. dest must have at
|
||||
* least size * 2 bytes; source must have size bytes. The result is not null-
|
||||
* terminated.
|
||||
*/
|
||||
void FileSpec::
|
||||
encode_hex(char *dest, const unsigned char *source, size_t size) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
int high = (source[i] >> 4) & 0xf;
|
||||
int low = source[i] & 0xf;
|
||||
dest[2 * i] = encode_hexdigit(high);
|
||||
dest[2 * i + 1] = encode_hexdigit(low);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the indicated buffer as a string of hex characters to the given
|
||||
* ostream.
|
||||
*/
|
||||
void FileSpec::
|
||||
stream_hex(ostream &out, const unsigned char *source, size_t size) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
int high = (source[i] >> 4) & 0xf;
|
||||
int low = source[i] & 0xf;
|
||||
out.put(encode_hexdigit(high));
|
||||
out.put(encode_hexdigit(low));
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file fileSpec.h
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
#ifndef FILESPEC_H
|
||||
#define FILESPEC_H
|
||||
|
||||
#include "get_tinyxml.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* This simple class is used both within the core API in this module, as well
|
||||
* as within the plugin_npapi plugin implementation, to represent a file on
|
||||
* disk that may need to be verified or (re)downloaded.
|
||||
*/
|
||||
class FileSpec {
|
||||
public:
|
||||
FileSpec();
|
||||
FileSpec(const FileSpec ©);
|
||||
void operator = (const FileSpec ©);
|
||||
~FileSpec();
|
||||
|
||||
void load_xml(TiXmlElement *xelement);
|
||||
void store_xml(TiXmlElement *xelement);
|
||||
|
||||
inline const std::string &get_filename() const;
|
||||
inline void set_filename(const std::string &filename);
|
||||
inline std::string get_pathname(const std::string &package_dir) const;
|
||||
inline size_t get_size() const;
|
||||
inline time_t get_timestamp() const;
|
||||
inline bool has_hash() const;
|
||||
|
||||
bool quick_verify(const std::string &package_dir);
|
||||
bool quick_verify_pathname(const std::string &pathname);
|
||||
bool full_verify(const std::string &package_dir);
|
||||
inline const FileSpec *get_actual_file() const;
|
||||
const FileSpec *force_get_actual_file(const std::string &pathname);
|
||||
|
||||
bool check_hash(const std::string &pathname) const;
|
||||
bool read_hash(const std::string &pathname);
|
||||
bool read_hash_stream(std::istream &in);
|
||||
int compare_hash(const FileSpec &other) const;
|
||||
|
||||
void write(std::ostream &out) const;
|
||||
void output_hash(std::ostream &out) const;
|
||||
|
||||
private:
|
||||
bool priv_check_hash(const std::string &pathname, void *stp);
|
||||
static inline int decode_hexdigit(char c);
|
||||
static inline char encode_hexdigit(int c);
|
||||
|
||||
static bool decode_hex(unsigned char *dest, const char *source, size_t size);
|
||||
static void encode_hex(char *dest, const unsigned char *source, size_t size);
|
||||
static void stream_hex(std::ostream &out, const unsigned char *source, size_t size);
|
||||
|
||||
enum { hash_size = 16 };
|
||||
|
||||
std::string _filename;
|
||||
size_t _size;
|
||||
time_t _timestamp;
|
||||
unsigned char _hash[hash_size];
|
||||
bool _got_hash;
|
||||
|
||||
FileSpec *_actual_file;
|
||||
};
|
||||
|
||||
#include "fileSpec.I"
|
||||
|
||||
#endif
|
||||
@@ -1,311 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file find_root_dir.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
#include "find_root_dir.h"
|
||||
#include "mkdir_complete.h"
|
||||
#include "get_tinyxml.h"
|
||||
#include "wstring_encode.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <pwd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
using std::cerr;
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
|
||||
#ifdef _WIN32
|
||||
// From KnownFolders.h (part of Vista SDK):
|
||||
#define DEFINE_KNOWN_FOLDER(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
DEFINE_KNOWN_FOLDER(FOLDERID_LocalAppData, 0xF1B32785, 0x6FBA, 0x4FCF, 0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91);
|
||||
DEFINE_KNOWN_FOLDER(FOLDERID_LocalAppDataLow, 0xA520A1A4, 0x1780, 0x4FF6, 0xBD, 0x18, 0x16, 0x73, 0x43, 0xC5, 0xAF, 0x16);
|
||||
DEFINE_KNOWN_FOLDER(FOLDERID_InternetCache, 0x352481E8, 0x33BE, 0x4251, 0xBA, 0x85, 0x60, 0x07, 0xCA, 0xED, 0xCF, 0x9D);
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* A wrapper around SHGetSpecialFolderPath(), to return the Panda3D directory
|
||||
* under the indicated CSIDL folder.
|
||||
*/
|
||||
static wstring
|
||||
get_csidl_dir_w(int csidl) {
|
||||
static const int buffer_size = MAX_PATH;
|
||||
wchar_t buffer[buffer_size];
|
||||
if (SHGetSpecialFolderPathW(nullptr, buffer, csidl, true)) {
|
||||
wstring root = buffer;
|
||||
root += wstring(L"/Panda3D");
|
||||
|
||||
if (mkdir_complete_w(root, cerr)) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
// Something went wrong.
|
||||
return wstring();
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Wide-character implementation of find_root_dir_default(), only needed for
|
||||
* Windows.
|
||||
*/
|
||||
static wstring
|
||||
find_root_dir_default_w() {
|
||||
// First, use IEIsProtectedModeProcess() to determine if we are running in
|
||||
// IE's "protected mode" under Vista.
|
||||
|
||||
wstring root;
|
||||
bool is_protected = false;
|
||||
HMODULE ieframe = LoadLibrary("ieframe.dll");
|
||||
if (ieframe != nullptr) {
|
||||
typedef HRESULT STDAPICALLTYPE IEIsProtectedModeProcess(BOOL *pbResult);
|
||||
IEIsProtectedModeProcess *func = (IEIsProtectedModeProcess *)GetProcAddress(ieframe, "IEIsProtectedModeProcess");
|
||||
if (func != nullptr) {
|
||||
BOOL result = false;
|
||||
HRESULT hr = (*func)(&result);
|
||||
if (hr == S_OK) {
|
||||
is_protected = (result != 0);
|
||||
}
|
||||
// Any other return value means some error, especially E_NOTIMPL, which
|
||||
// means we're not running under Vista. In this case we can assume
|
||||
// we're not running in protected mode.
|
||||
}
|
||||
|
||||
if (is_protected) {
|
||||
// If we *are* running in protected mode, we need to use
|
||||
// FOLDERID_LocalAppDataLow.
|
||||
|
||||
// We should be able to use IEGetWriteableFolderPath() to query this
|
||||
// folder, but for some reason, that function returns E_ACCESSDENIED on
|
||||
// FOLDERID_LocalAppDataLow, even though this is certainly a folder we
|
||||
// have write access to.
|
||||
|
||||
// Well, SHGetKnownFolderPath() does work. This function only exists on
|
||||
// Vista and above, though, so we still have to pull it out of the DLL
|
||||
// instead of hard-linking it.
|
||||
|
||||
HMODULE shell32 = LoadLibrary("shell32.dll");
|
||||
if (shell32 != nullptr) {
|
||||
typedef HRESULT STDAPICALLTYPE SHGetKnownFolderPath(REFGUID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
|
||||
SHGetKnownFolderPath *func = (SHGetKnownFolderPath *)GetProcAddress(shell32, "SHGetKnownFolderPath");
|
||||
if (func != nullptr) {
|
||||
LPWSTR cache_path = nullptr;
|
||||
HRESULT hr = (*func)(FOLDERID_LocalAppDataLow, 0, nullptr, &cache_path);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
root = cache_path;
|
||||
CoTaskMemFree(cache_path);
|
||||
|
||||
root += wstring(L"/Panda3D");
|
||||
if (mkdir_complete_w(root, cerr)) {
|
||||
FreeLibrary(shell32);
|
||||
FreeLibrary(ieframe);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
FreeLibrary(shell32);
|
||||
}
|
||||
|
||||
// Couldn't get FOLDERID_LocalAppDataLow for some reason. We're in
|
||||
// fallback mode now. Use IEGetWriteableFolderPath to get the standard
|
||||
// cache folder.
|
||||
typedef HRESULT STDAPICALLTYPE IEGetWriteableFolderPath(REFGUID clsidFolderID, LPWSTR* lppwstrPath);
|
||||
IEGetWriteableFolderPath *func = (IEGetWriteableFolderPath *)GetProcAddress(ieframe, "IEGetWriteableFolderPath");
|
||||
if (func != nullptr) {
|
||||
LPWSTR cache_path = nullptr;
|
||||
|
||||
// Since we're here, we'll start by asking for LocalAppDataLow, even
|
||||
// though I know it doesn't work.
|
||||
HRESULT hr = (*func)(FOLDERID_LocalAppDataLow, &cache_path);
|
||||
if (FAILED(hr)) {
|
||||
// This one should work.
|
||||
hr = (*func)(FOLDERID_InternetCache, &cache_path);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
root = cache_path;
|
||||
CoTaskMemFree(cache_path);
|
||||
root += wstring(L"/Panda3D");
|
||||
if (mkdir_complete_w(root, cerr)) {
|
||||
FreeLibrary(ieframe);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeLibrary(ieframe);
|
||||
}
|
||||
|
||||
// All right, here we are in the normal, unprotected mode. This is also the
|
||||
// normal XP codepath.
|
||||
|
||||
// e.g., c:Documents and Settings<username>Local SettingsApplication
|
||||
// DataPanda3D
|
||||
root = get_csidl_dir_w(CSIDL_LOCAL_APPDATA);
|
||||
if (!root.empty()) {
|
||||
return root;
|
||||
}
|
||||
|
||||
// For some crazy reason, we can't get CSIDL_LOCAL_APPDATA. Fall back to
|
||||
// the cache folder.
|
||||
|
||||
// e.g. c:Documents and Settings<username>Local SettingsTemporary Internet
|
||||
// FilesPanda3D
|
||||
root = get_csidl_dir_w(CSIDL_INTERNET_CACHE);
|
||||
if (!root.empty()) {
|
||||
return root;
|
||||
}
|
||||
|
||||
// If we couldn't get any of those folders, huh. Punt and try for the old
|
||||
// standby GetTempPath, for lack of anything better.
|
||||
static const int buffer_size = MAX_PATH;
|
||||
wchar_t buffer[buffer_size];
|
||||
if (GetTempPathW(buffer_size, buffer) != 0) {
|
||||
root = buffer;
|
||||
root += wstring(L"Panda3D");
|
||||
if (mkdir_complete_w(root, cerr)) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
return wstring();
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to the system-default for the root directory. This is
|
||||
* where we look first.
|
||||
*/
|
||||
static string
|
||||
find_root_dir_default() {
|
||||
#ifdef _WIN32
|
||||
wstring root_w = find_root_dir_default_w();
|
||||
if (!root_w.empty()) {
|
||||
string root;
|
||||
if (wstring_to_string(root, root_w)) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
// e.g., Users<username>LibraryCachesPanda3D
|
||||
string root = find_osx_root_dir();
|
||||
if (!root.empty()) {
|
||||
return root;
|
||||
}
|
||||
|
||||
#else // The Linux/*BSD case
|
||||
// e.g., home<username>.panda3d
|
||||
|
||||
string root;
|
||||
const passwd *pwdata = getpwuid(getuid());
|
||||
if (pwdata == nullptr) {
|
||||
char *home = getenv("HOME");
|
||||
if (home == nullptr) {
|
||||
// Beh. Let's hope it never gets to this point.
|
||||
return ".";
|
||||
} else {
|
||||
root = home;
|
||||
}
|
||||
} else {
|
||||
root = pwdata->pw_dir;
|
||||
}
|
||||
|
||||
root += "/.panda3d";
|
||||
if (mkdir(root.c_str(), 0700) == 0 || errno == EEXIST) {
|
||||
return root;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Couldn't find a directory. Punt.
|
||||
return ".";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the path to the installable Panda3D directory on the user's
|
||||
* machine.
|
||||
*/
|
||||
static string
|
||||
find_root_dir_actual() {
|
||||
string root = find_root_dir_default();
|
||||
|
||||
// Now look for a config.xml file in that directory, which might redirect us
|
||||
// elsewhere.
|
||||
string config_filename = root + "/config.xml";
|
||||
TiXmlDocument doc(config_filename);
|
||||
if (!doc.LoadFile()) {
|
||||
// No config.xml found, or not valid xml.
|
||||
return root;
|
||||
}
|
||||
|
||||
TiXmlElement *xconfig = doc.FirstChildElement("config");
|
||||
if (xconfig == nullptr) {
|
||||
// No <config> element within config.xml.
|
||||
return root;
|
||||
}
|
||||
|
||||
const char *new_root = xconfig->Attribute("root_dir");
|
||||
if (new_root == nullptr || *new_root == '\0') {
|
||||
// No root_dir specified.
|
||||
return root;
|
||||
}
|
||||
|
||||
if (!mkdir_complete(new_root, cerr)) {
|
||||
// The specified root_dir wasn't valid.
|
||||
return root;
|
||||
}
|
||||
|
||||
// We've been redirected to another location. Respect that.
|
||||
return new_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the public interface to the above functions.
|
||||
*/
|
||||
string
|
||||
find_root_dir() {
|
||||
string root = find_root_dir_actual();
|
||||
|
||||
#ifdef _WIN32
|
||||
// Now map that (possibly utf-8) filename into its 8.3 equivalent, so we can
|
||||
// safely pass it around to Python and other tools that might not understand
|
||||
// Unicode filenames. Silly Windows, creating an entirely new and
|
||||
// incompatible kind of filename.
|
||||
wstring root_w;
|
||||
string_to_wstring(root_w, root);
|
||||
|
||||
DWORD length = GetShortPathNameW(root_w.c_str(), nullptr, 0);
|
||||
wchar_t *short_name = new wchar_t[length];
|
||||
GetShortPathNameW(root_w.c_str(), short_name, length);
|
||||
|
||||
wstring_to_string(root, short_name);
|
||||
delete[] short_name;
|
||||
#endif // _WIN32
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file find_root_dir.h
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
#ifndef FIND_ROOT_DIR_H
|
||||
#define FIND_ROOT_DIR_H
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
std::string find_root_dir();
|
||||
|
||||
#ifdef __APPLE__
|
||||
std::string find_osx_root_dir();
|
||||
#endif // __APPLE__
|
||||
|
||||
#endif
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file find_root_dir_assist.mm
|
||||
* @author drose
|
||||
* @date 2009-04-13
|
||||
*/
|
||||
|
||||
#include "find_root_dir.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
/**
|
||||
* Copy the Objective-C string to a C++ string.
|
||||
*/
|
||||
static string
|
||||
NSString_to_cpp_string(NSString *str) {
|
||||
size_t length = [str length];
|
||||
string result;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
result += (char)[str characterAtIndex: i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static string
|
||||
call_NSSearchPathForDirectories(NSSearchPathDirectory dirkey, NSSearchPathDomainMask domain) {
|
||||
// Ensure that Carbon has been initialized, and that we have an auto-release
|
||||
// pool.
|
||||
NSApplicationLoad();
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(dirkey, domain, YES);
|
||||
string result;
|
||||
if ([paths count] != 0) {
|
||||
result = NSString_to_cpp_string([paths objectAtIndex:0]);
|
||||
}
|
||||
[pool release];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static string
|
||||
get_osx_home_directory() {
|
||||
NSApplicationLoad();
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSString *dir = NSHomeDirectory();
|
||||
string result = NSString_to_cpp_string(dir);
|
||||
[pool release];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
std::string
|
||||
find_osx_root_dir() {
|
||||
string result = call_NSSearchPathForDirectories(NSCachesDirectory, NSUserDomainMask);
|
||||
if (!result.empty()) {
|
||||
return result + "/Panda3D";
|
||||
}
|
||||
result = get_osx_home_directory();
|
||||
if (!result.empty()) {
|
||||
return result + "/Panda3D";
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file get_tinyxml.h
|
||||
* @author drose
|
||||
* @date 2009-07-01
|
||||
*/
|
||||
|
||||
#ifndef GET_TINYXML_H
|
||||
#define GET_TINYXML_H
|
||||
|
||||
// This header file exists just to include tinyxml.h safely. We need this
|
||||
// since tinyxml.h requires having the symbol TIXML_USE_STL already defined
|
||||
// before you include it.
|
||||
|
||||
#ifndef TIXML_USE_STL
|
||||
#define TIXML_USE_STL
|
||||
#endif
|
||||
|
||||
#include <tinyxml.h>
|
||||
|
||||
#endif
|
||||
@@ -1,963 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file get_twirl_data.cxx
|
||||
* @author drose
|
||||
* @date 2011-08-24
|
||||
*/
|
||||
|
||||
#include "get_twirl_data.h"
|
||||
#include <string.h>
|
||||
|
||||
struct twirl_flip {
|
||||
int _index;
|
||||
bool _flip_x;
|
||||
bool _flip_y;
|
||||
bool _exchange;
|
||||
};
|
||||
|
||||
static twirl_flip twirl_flip_table[twirl_num_steps + 1] = {
|
||||
{ 0, false, false, false }, // 0
|
||||
{ 1, false, false, false }, // 30
|
||||
{ 2, false, false, false }, // 60
|
||||
{ 0, false, true, true }, // 90
|
||||
{ 1, false, true, true }, // 120
|
||||
{ 2, false, true, true }, // 150
|
||||
{ 0, true, true, false }, // 180
|
||||
{ 1, true, true, false }, // 210
|
||||
{ 2, true, true, false }, // 240
|
||||
{ 0, true, false, true }, // 270
|
||||
{ 1, true, false, true }, // 300
|
||||
{ 2, true, false, true }, // 330
|
||||
{ 3, false, false, false } // the "failed" icon
|
||||
};
|
||||
|
||||
// These tables are the raw data for the twirl icon data. They're extracted
|
||||
// with bin2c from the three layers of twirl.xcf, in this directory.
|
||||
static const unsigned char twirl_0_data[] = {
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xe4, 0xe4, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xfe, 0xfd, 0xff, 0xa5, 0x1f, 0x1f, 0xa5, 0xff, 0xfd, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xd7, 0x16, 0x00, 0x00,
|
||||
0x16, 0xd7, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xeb, 0xba, 0xe6, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0x91, 0x00, 0x05, 0x05, 0x00, 0x91, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xfd, 0xfa, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xd7, 0x2f, 0x06, 0x23, 0xa6, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x66, 0x00, 0x04, 0x04, 0x00, 0x66, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xf8, 0xec, 0xe8, 0xed, 0xfb, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x80, 0x00,
|
||||
0x0a, 0x04, 0x0c, 0xb0, 0xff, 0xfe, 0xff, 0xfb, 0x5c, 0x00, 0x03,
|
||||
0x03, 0x00, 0x5c, 0xfb, 0xff, 0xfe, 0xff, 0xf9, 0xe9, 0xe8, 0xe8,
|
||||
0xe8, 0xf3, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0x7c, 0x08, 0x11, 0x14, 0x03, 0x30, 0xe6, 0xff, 0xff,
|
||||
0xfb, 0x5c, 0x00, 0x03, 0x03, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0xfd,
|
||||
0xed, 0xe8, 0xe9, 0xe9, 0xe8, 0xf3, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xab, 0x0a, 0x0f, 0x15, 0x13,
|
||||
0x07, 0x79, 0xff, 0xff, 0xfd, 0x62, 0x00, 0x03, 0x03, 0x00, 0x61,
|
||||
0xfd, 0xff, 0xff, 0xf4, 0xe8, 0xe9, 0xea, 0xe9, 0xe9, 0xf7, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xf1,
|
||||
0x2c, 0x0a, 0x14, 0x15, 0x0d, 0x1e, 0xcb, 0xff, 0xff, 0x79, 0x00,
|
||||
0x04, 0x04, 0x00, 0x79, 0xff, 0xff, 0xfb, 0xeb, 0xe9, 0xea, 0xe9,
|
||||
0xe9, 0xeb, 0xfd, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0x97, 0x04, 0x14, 0x15, 0x18, 0x00, 0x82,
|
||||
0xff, 0xff, 0x99, 0x00, 0x03, 0x03, 0x00, 0x99, 0xff, 0xff, 0xf4,
|
||||
0xe7, 0xea, 0xea, 0xe9, 0xe8, 0xf5, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf2, 0x42, 0x03,
|
||||
0x17, 0x16, 0x0b, 0x39, 0xe9, 0xff, 0xb5, 0x01, 0x00, 0x00, 0x01,
|
||||
0xb6, 0xff, 0xfd, 0xee, 0xe9, 0xea, 0xea, 0xe8, 0xed, 0xfd, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xdd, 0x8b, 0x84, 0xad, 0xee, 0xff, 0xff,
|
||||
0xff, 0xff, 0xc2, 0x18, 0x0b, 0x15, 0x13, 0x09, 0xbf, 0xff, 0xda,
|
||||
0x25, 0x00, 0x00, 0x25, 0xd9, 0xff, 0xf9, 0xe9, 0xe9, 0xea, 0xe9,
|
||||
0xe9, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xf0, 0xe7, 0xe8, 0xf8,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x48, 0x17, 0x1f,
|
||||
0x20, 0x3b, 0x98, 0xee, 0xff, 0xff, 0xff, 0xa2, 0x0f, 0x0e, 0x15,
|
||||
0x05, 0x7a, 0xff, 0xff, 0x54, 0x00, 0x00, 0x53, 0xff, 0xff, 0xf3,
|
||||
0xe8, 0xea, 0xe9, 0xe9, 0xf5, 0xff, 0xfe, 0xff, 0xfc, 0xec, 0xd9,
|
||||
0xd3, 0xd2, 0xd1, 0xd9, 0xfb, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xcc, 0x1f, 0x20, 0x28, 0x26, 0x22, 0x1a, 0x4c, 0xbf, 0xff, 0xff,
|
||||
0xff, 0x86, 0x02, 0x0e, 0x09, 0x41, 0xfa, 0xff, 0x8d, 0x00, 0x00,
|
||||
0x8d, 0xff, 0xfa, 0xee, 0xe8, 0xe9, 0xe8, 0xf3, 0xff, 0xff, 0xff,
|
||||
0xf4, 0xdd, 0xd2, 0xd3, 0xd3, 0xd4, 0xd3, 0xd2, 0xf2, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xf0, 0x3f, 0x1a, 0x2a, 0x2b, 0x2a, 0x2a,
|
||||
0x1c, 0x28, 0xa1, 0xff, 0xff, 0xff, 0x76, 0x02, 0x04, 0x1e, 0xee,
|
||||
0xff, 0xd7, 0x07, 0x07, 0xd7, 0xff, 0xfc, 0xeb, 0xe8, 0xe8, 0xf1,
|
||||
0xff, 0xff, 0xff, 0xee, 0xd5, 0xd1, 0xd4, 0xd4, 0xd5, 0xd4, 0xd1,
|
||||
0xd7, 0xfa, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xba, 0x28,
|
||||
0x1a, 0x28, 0x2b, 0x2a, 0x2d, 0x24, 0x23, 0x87, 0xff, 0xff, 0xff,
|
||||
0x80, 0x01, 0x0b, 0xc5, 0xff, 0xff, 0x24, 0x24, 0xff, 0xff, 0xfa,
|
||||
0xe9, 0xe7, 0xf2, 0xfe, 0xff, 0xff, 0xe9, 0xd4, 0xd3, 0xd5, 0xd5,
|
||||
0xd5, 0xd4, 0xd1, 0xd3, 0xef, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xc0, 0x4a, 0x1f, 0x22, 0x2e, 0x2c, 0x2b, 0x25,
|
||||
0x18, 0x79, 0xfb, 0xff, 0xff, 0x97, 0x0b, 0x7b, 0xff, 0xff, 0x9b,
|
||||
0x9b, 0xff, 0xff, 0xf4, 0xe8, 0xf4, 0xff, 0xff, 0xff, 0xe6, 0xd1,
|
||||
0xd3, 0xd5, 0xd5, 0xd5, 0xd3, 0xd2, 0xda, 0xf1, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xef, 0x8e, 0x38,
|
||||
0x17, 0x21, 0x29, 0x2b, 0x26, 0x17, 0x81, 0xff, 0xff, 0xff, 0xa0,
|
||||
0x94, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf5, 0xf5, 0xff, 0xff,
|
||||
0xff, 0xe8, 0xd1, 0xd3, 0xd5, 0xd4, 0xd3, 0xd1, 0xd7, 0xe7, 0xfa,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xd6, 0x95, 0x53, 0x22, 0x1c, 0x20, 0x1c, 0x15,
|
||||
0x96, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xec, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xdb,
|
||||
0xe9, 0xf6, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xee, 0xca,
|
||||
0x8d, 0x58, 0x35, 0x24, 0x1e, 0x9f, 0xff, 0xfb, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xed, 0xd3, 0xd3, 0xd6,
|
||||
0xdc, 0xe7, 0xf3, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xf3, 0xd1, 0x8f, 0x9c, 0xfc,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xec, 0xe7, 0xf5, 0xfc, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xe1, 0xac,
|
||||
0x8d, 0x85, 0x85, 0x89, 0x9b, 0xb3, 0xc8, 0xe3, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5,
|
||||
0xec, 0xe5, 0xdd, 0xd7, 0xd6, 0xd6, 0xd9, 0xe3, 0xf5, 0xff, 0xff,
|
||||
0xff, 0xbb, 0x50, 0x30, 0x2f, 0x33, 0x33, 0x32, 0x2f, 0x30, 0x41,
|
||||
0x5b, 0x7e, 0xaa, 0xe1, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xf5, 0xe3, 0xd4, 0xc8, 0xbf, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xb9,
|
||||
0xba, 0xc4, 0xe8, 0xff, 0xeb, 0x57, 0x31, 0x44, 0x42, 0x42, 0x42,
|
||||
0x42, 0x43, 0x42, 0x3e, 0x38, 0x30, 0x36, 0x45, 0x5c, 0xb4, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xe6, 0xc8, 0xc0, 0xbb, 0xb9, 0xbc, 0xbe, 0xbf, 0xc0,
|
||||
0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xb9, 0xc7, 0xf8, 0xeb, 0x57, 0x31,
|
||||
0x44, 0x42, 0x42, 0x42, 0x42, 0x43, 0x42, 0x3e, 0x38, 0x30, 0x36,
|
||||
0x46, 0x5c, 0xb4, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xe6, 0xc8, 0xc0, 0xbb, 0xb9,
|
||||
0xbc, 0xbe, 0xbf, 0xc0, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xb9, 0xc7,
|
||||
0xf8, 0xff, 0xbb, 0x50, 0x30, 0x2f, 0x33, 0x33, 0x32, 0x2f, 0x30,
|
||||
0x41, 0x5b, 0x7e, 0xaa, 0xe1, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xf5, 0xe3, 0xd4, 0xc8, 0xbf, 0xb9, 0xb9, 0xba, 0xba, 0xba,
|
||||
0xb9, 0xba, 0xc4, 0xe8, 0xff, 0xff, 0xff, 0xe1, 0xac, 0x8d, 0x85,
|
||||
0x85, 0x89, 0x9b, 0xb3, 0xc8, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xec, 0xe5,
|
||||
0xdd, 0xd7, 0xd6, 0xd6, 0xd9, 0xe3, 0xf5, 0xff, 0xff, 0xfe, 0xfd,
|
||||
0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfb, 0xf2, 0xd5, 0x9f, 0xb2, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xd7, 0xd2, 0xed, 0xfa, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xef, 0xd0, 0x9f, 0x74, 0x5b, 0x4e, 0x4d, 0xba, 0xff, 0xfc,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xd9,
|
||||
0xa5, 0xa7, 0xae, 0xbc, 0xd1, 0xea, 0xf8, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xda, 0xa4, 0x6f, 0x4c, 0x49, 0x4c, 0x48, 0x46,
|
||||
0xb3, 0xff, 0xff, 0xff, 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xd5, 0xa1, 0xa4, 0xa5, 0xa4, 0xa6, 0xb9,
|
||||
0xd4, 0xef, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xed, 0x9e, 0x5c, 0x44, 0x4e, 0x53,
|
||||
0x55, 0x50, 0x47, 0xa3, 0xff, 0xff, 0xff, 0xbc, 0xba, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xcf, 0xd4, 0xff, 0xff, 0xff, 0xcd, 0xa2,
|
||||
0xa8, 0xaa, 0xa9, 0xa6, 0xa1, 0xaf, 0xd2, 0xf8, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xc5, 0x69, 0x4b,
|
||||
0x4f, 0x57, 0x56, 0x55, 0x50, 0x47, 0x9c, 0xff, 0xff, 0xff, 0xb5,
|
||||
0x61, 0xb0, 0xff, 0xff, 0xcd, 0xcd, 0xff, 0xff, 0xc3, 0x90, 0xd0,
|
||||
0xff, 0xff, 0xfd, 0xc9, 0xa2, 0xa7, 0xaa, 0xaa, 0xab, 0xa6, 0xa5,
|
||||
0xb6, 0xe5, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xbe, 0x4f, 0x48, 0x53, 0x55, 0x55, 0x56, 0x4e, 0x50, 0xa7, 0xff,
|
||||
0xff, 0xfc, 0xa6, 0x5b, 0x65, 0xde, 0xff, 0xff, 0x92, 0x92, 0xff,
|
||||
0xff, 0xe5, 0x90, 0x8c, 0xc6, 0xff, 0xff, 0xff, 0xcf, 0xa6, 0xa7,
|
||||
0xaa, 0xa9, 0xaa, 0xa9, 0xa3, 0xa9, 0xe3, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xed, 0x5f, 0x48, 0x54, 0x55, 0x54, 0x54, 0x47,
|
||||
0x57, 0xbb, 0xff, 0xff, 0xff, 0xa1, 0x5c, 0x5f, 0x72, 0xf7, 0xff,
|
||||
0xeb, 0x83, 0x83, 0xeb, 0xff, 0xf7, 0x99, 0x8d, 0x8c, 0xc1, 0xff,
|
||||
0xff, 0xff, 0xd9, 0xa9, 0xa4, 0xa9, 0xa9, 0xaa, 0xa9, 0xa3, 0xb2,
|
||||
0xf9, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xcd, 0x4a, 0x4d, 0x52,
|
||||
0x50, 0x4d, 0x49, 0x76, 0xd3, 0xff, 0xff, 0xff, 0xab, 0x5c, 0x66,
|
||||
0x62, 0x8a, 0xfd, 0xff, 0xc7, 0x79, 0x79, 0xc7, 0xff, 0xfd, 0xa8,
|
||||
0x8f, 0x92, 0x8c, 0xc8, 0xff, 0xff, 0xff, 0xe6, 0xb7, 0xa3, 0xa6,
|
||||
0xa7, 0xa8, 0xa5, 0xa5, 0xeb, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xf0, 0x67, 0x45, 0x4b, 0x4d, 0x65, 0xb3, 0xf5, 0xff, 0xff, 0xff,
|
||||
0xbd, 0x64, 0x66, 0x6a, 0x5f, 0xaf, 0xff, 0xff, 0xa9, 0x75, 0x75,
|
||||
0xa9, 0xff, 0xff, 0xc3, 0x8d, 0x95, 0x91, 0x92, 0xd5, 0xff, 0xff,
|
||||
0xff, 0xf8, 0xd5, 0xb0, 0xa5, 0xa5, 0xa2, 0xb5, 0xfa, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xe2, 0xa3, 0xa0, 0xc2, 0xf5, 0xff,
|
||||
0xff, 0xff, 0xff, 0xd2, 0x68, 0x65, 0x6a, 0x68, 0x64, 0xd9, 0xff,
|
||||
0xed, 0x92, 0x7a, 0x7a, 0x92, 0xed, 0xff, 0xe2, 0x90, 0x94, 0x95,
|
||||
0x90, 0x96, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xde, 0xcd, 0xd0,
|
||||
0xf1, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf3, 0x81, 0x5f, 0x6b, 0x6b,
|
||||
0x63, 0x86, 0xf3, 0xff, 0xda, 0x81, 0x7f, 0x7f, 0x81, 0xda, 0xff,
|
||||
0xf5, 0xa5, 0x90, 0x95, 0x96, 0x8c, 0xa9, 0xf9, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xb7,
|
||||
0x5e, 0x69, 0x69, 0x6c, 0x5b, 0xb4, 0xff, 0xff, 0xcc, 0x75, 0x81,
|
||||
0x81, 0x75, 0xcc, 0xff, 0xff, 0xc6, 0x8a, 0x96, 0x95, 0x94, 0x8d,
|
||||
0xcf, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf3, 0x75, 0x63, 0x69, 0x6a, 0x64, 0x73, 0xe2, 0xff,
|
||||
0xff, 0xbc, 0x75, 0x81, 0x81, 0x75, 0xbc, 0xff, 0xff, 0xe7, 0x99,
|
||||
0x91, 0x95, 0x94, 0x90, 0x9f, 0xf8, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xc5, 0x62, 0x66, 0x6a, 0x68,
|
||||
0x62, 0xb0, 0xff, 0xff, 0xfe, 0xb0, 0x77, 0x81, 0x81, 0x77, 0xb0,
|
||||
0xfe, 0xff, 0xff, 0xc2, 0x8e, 0x94, 0x95, 0x92, 0x90, 0xd9, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xa9,
|
||||
0x61, 0x68, 0x69, 0x5e, 0x80, 0xf4, 0xff, 0xff, 0xfd, 0xae, 0x77,
|
||||
0x81, 0x81, 0x77, 0xae, 0xfd, 0xff, 0xff, 0xf4, 0xa1, 0x8d, 0x94,
|
||||
0x93, 0x8f, 0xc4, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xad, 0x5c, 0x62, 0x5e, 0x68, 0xd3, 0xff, 0xfe,
|
||||
0xff, 0xfd, 0xae, 0x77, 0x81, 0x81, 0x77, 0xae, 0xfd, 0xff, 0xfe,
|
||||
0xff, 0xdb, 0x91, 0x8d, 0x90, 0x8b, 0xc5, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xe7, 0x7f, 0x62, 0x78,
|
||||
0xce, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x75, 0x81, 0x81, 0x75,
|
||||
0xb3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd6, 0x9b, 0x8e, 0xa0, 0xed,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xf6, 0xdc, 0xf4, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xc8,
|
||||
0x75, 0x82, 0x82, 0x75, 0xc8, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xf4, 0xe0, 0xf6, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xff, 0xeb, 0x8a, 0x76, 0x76, 0x8a, 0xeb, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xd2, 0x8f, 0x8f,
|
||||
0xd2, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf1, 0xf1, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
static const unsigned char twirl_30_data[] = {
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xe7, 0xe7, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xfe, 0xfd, 0xff, 0xac, 0x31, 0x31, 0xac, 0xff, 0xfd, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xda, 0x28, 0x02, 0x02,
|
||||
0x28, 0xda, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xed, 0xc1, 0xe9, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0x9a, 0x02, 0x1a, 0x1a, 0x02, 0x9a, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xed, 0xc2, 0xef, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xdb, 0x42, 0x1d, 0x37, 0xae, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x73, 0x01, 0x18, 0x18, 0x01, 0x73, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xab, 0x18, 0x00, 0x23, 0xd7, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x8c, 0x18,
|
||||
0x21, 0x1b, 0x23, 0xb7, 0xff, 0xfe, 0xff, 0xfb, 0x6a, 0x05, 0x17,
|
||||
0x17, 0x05, 0x6a, 0xfb, 0xff, 0xfe, 0xff, 0xb4, 0x00, 0x00, 0x00,
|
||||
0x00, 0x73, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0x88, 0x1f, 0x28, 0x2a, 0x1b, 0x44, 0xe8, 0xff, 0xff,
|
||||
0xfb, 0x6a, 0x05, 0x17, 0x17, 0x05, 0x6a, 0xfb, 0xff, 0xff, 0xec,
|
||||
0x25, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xb4, 0x21, 0x25, 0x2b, 0x29,
|
||||
0x1e, 0x86, 0xff, 0xff, 0xfd, 0x6e, 0x04, 0x17, 0x17, 0x04, 0x6e,
|
||||
0xfd, 0xff, 0xff, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xf3,
|
||||
0x3f, 0x21, 0x2a, 0x2b, 0x24, 0x34, 0xd0, 0xff, 0xff, 0x85, 0x00,
|
||||
0x18, 0x18, 0x00, 0x84, 0xff, 0xff, 0xce, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x13, 0xea, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xa1, 0x1b, 0x2a, 0x2b, 0x2e, 0x16, 0x8e,
|
||||
0xff, 0xff, 0xa2, 0x01, 0x18, 0x18, 0x01, 0xa2, 0xff, 0xff, 0x7f,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x83, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf3, 0x54, 0x1b,
|
||||
0x2d, 0x2c, 0x22, 0x4b, 0xeb, 0xff, 0xbc, 0x16, 0x13, 0x13, 0x16,
|
||||
0xbc, 0xff, 0xea, 0x2f, 0x00, 0x02, 0x02, 0x00, 0x28, 0xeb, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xe0, 0x96, 0x91, 0xb5, 0xf0, 0xff, 0xff,
|
||||
0xff, 0xff, 0xc8, 0x2e, 0x22, 0x2b, 0x29, 0x20, 0xc5, 0xff, 0xdd,
|
||||
0x37, 0x0b, 0x0b, 0x36, 0xdd, 0xff, 0xc0, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0xb2, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf7, 0xf3, 0xf3, 0xfb,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x5a, 0x2e, 0x35,
|
||||
0x36, 0x4e, 0xa2, 0xf0, 0xff, 0xff, 0xff, 0xab, 0x26, 0x24, 0x2b,
|
||||
0x1c, 0x87, 0xff, 0xff, 0x62, 0x02, 0x02, 0x61, 0xff, 0xff, 0x76,
|
||||
0x00, 0x00, 0x00, 0x00, 0x8d, 0xff, 0xff, 0xff, 0xfd, 0xf6, 0xec,
|
||||
0xe8, 0xe8, 0xe8, 0xec, 0xfd, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xd1, 0x35, 0x36, 0x3d, 0x3b, 0x38, 0x31, 0x5d, 0xc5, 0xff, 0xff,
|
||||
0xff, 0x91, 0x1a, 0x25, 0x20, 0x52, 0xfb, 0xff, 0x97, 0x09, 0x09,
|
||||
0x97, 0xff, 0xfc, 0x36, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xff,
|
||||
0xf9, 0xee, 0xe8, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xf9, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xf2, 0x52, 0x31, 0x3f, 0x40, 0x3f, 0x3f,
|
||||
0x32, 0x3d, 0xaa, 0xff, 0xff, 0xff, 0x83, 0x19, 0x1b, 0x33, 0xef,
|
||||
0xff, 0xdb, 0x1c, 0x1c, 0xdb, 0xff, 0xf1, 0x0d, 0x00, 0x00, 0x5e,
|
||||
0xff, 0xff, 0xff, 0xf7, 0xea, 0xe8, 0xe9, 0xe9, 0xea, 0xe9, 0xe8,
|
||||
0xeb, 0xfd, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xc0, 0x3d,
|
||||
0x30, 0x3d, 0x40, 0x3f, 0x41, 0x39, 0x38, 0x93, 0xff, 0xff, 0xff,
|
||||
0x8c, 0x19, 0x22, 0xcb, 0xff, 0xff, 0x36, 0x37, 0xff, 0xff, 0xc8,
|
||||
0x00, 0x00, 0x68, 0xfb, 0xff, 0xff, 0xf4, 0xe9, 0xe9, 0xea, 0xea,
|
||||
0xea, 0xe9, 0xe8, 0xe9, 0xf7, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xc7, 0x5c, 0x35, 0x38, 0x42, 0x41, 0x40, 0x3b,
|
||||
0x2f, 0x86, 0xfb, 0xff, 0xff, 0xa1, 0x22, 0x88, 0xff, 0xff, 0xa3,
|
||||
0xa3, 0xff, 0xff, 0x77, 0x00, 0x81, 0xff, 0xff, 0xfe, 0xf2, 0xe8,
|
||||
0xe9, 0xea, 0xea, 0xea, 0xe9, 0xe8, 0xec, 0xf8, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xf0, 0x99, 0x4c,
|
||||
0x2e, 0x37, 0x3e, 0x40, 0x3b, 0x2e, 0x8e, 0xff, 0xff, 0xff, 0xa9,
|
||||
0x9e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x89, 0x8b, 0xff, 0xff,
|
||||
0xff, 0xf3, 0xe8, 0xe9, 0xea, 0xe9, 0xe9, 0xe8, 0xeb, 0xf3, 0xfd,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xda, 0x9f, 0x63, 0x38, 0x32, 0x36, 0x32, 0x2c,
|
||||
0xa0, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfc, 0xff, 0xff, 0xff, 0xf5, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xed,
|
||||
0xf4, 0xfa, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xcf,
|
||||
0x99, 0x69, 0x4a, 0x3a, 0x34, 0xa9, 0xff, 0xfb, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfc, 0xff, 0xf6, 0xe9, 0xe9, 0xea,
|
||||
0xee, 0xf3, 0xf9, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf4, 0xd5, 0x9a, 0xa6, 0xfc,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xf5, 0xf3, 0xf9, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xe4, 0xb5,
|
||||
0x99, 0x93, 0x92, 0x96, 0xa6, 0xbb, 0xce, 0xe6, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8,
|
||||
0xf2, 0xee, 0xe8, 0xe5, 0xe4, 0xe4, 0xe6, 0xed, 0xf8, 0xff, 0xff,
|
||||
0xff, 0xc3, 0x63, 0x47, 0x46, 0x49, 0x49, 0x49, 0x46, 0x46, 0x56,
|
||||
0x6d, 0x8d, 0xb3, 0xe4, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xf8, 0xec, 0xe2, 0xdb, 0xd5, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd1,
|
||||
0xd1, 0xd8, 0xf0, 0xff, 0xed, 0x6a, 0x47, 0x58, 0x57, 0x57, 0x57,
|
||||
0x57, 0x57, 0x57, 0x53, 0x4e, 0x47, 0x4c, 0x5a, 0x6d, 0xbc, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xee, 0xdb, 0xd6, 0xd2, 0xd1, 0xd3, 0xd4, 0xd5, 0xd5,
|
||||
0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd1, 0xda, 0xfa, 0xed, 0x69, 0x47,
|
||||
0x58, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x53, 0x4e, 0x47, 0x4c,
|
||||
0x5a, 0x6d, 0xbc, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xee, 0xdb, 0xd6, 0xd2, 0xd1,
|
||||
0xd3, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd1, 0xda,
|
||||
0xfa, 0xff, 0xc3, 0x63, 0x47, 0x46, 0x49, 0x49, 0x49, 0x46, 0x46,
|
||||
0x56, 0x6d, 0x8d, 0xb3, 0xe4, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xf8, 0xec, 0xe2, 0xdb, 0xd5, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2,
|
||||
0xd1, 0xd1, 0xd8, 0xf0, 0xff, 0xff, 0xff, 0xe4, 0xb5, 0x99, 0x93,
|
||||
0x92, 0x96, 0xa6, 0xbb, 0xce, 0xe6, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xf2, 0xee,
|
||||
0xe8, 0xe5, 0xe4, 0xe4, 0xe6, 0xed, 0xf8, 0xff, 0xff, 0xfe, 0xfd,
|
||||
0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfc, 0xf4, 0xda, 0xab, 0xbb, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xe1, 0xdd, 0xf1, 0xfb, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xf1, 0xd6, 0xaa, 0x86, 0x6f, 0x63, 0x63, 0xc2, 0xff, 0xfc,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xe2,
|
||||
0xbb, 0xbd, 0xc2, 0xcd, 0xdd, 0xef, 0xf9, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xde, 0xb0, 0x80, 0x62, 0x60, 0x62, 0x5f, 0x5d,
|
||||
0xbd, 0xff, 0xff, 0xff, 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xdf, 0xb8, 0xba, 0xbb, 0xba, 0xbc, 0xca,
|
||||
0xdf, 0xf3, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xef, 0xaa, 0x70, 0x5b, 0x64, 0x69,
|
||||
0x6a, 0x66, 0x5e, 0xae, 0xff, 0xff, 0xff, 0xc5, 0xc4, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xd8, 0xdc, 0xff, 0xff, 0xff, 0xd9, 0xb9,
|
||||
0xbd, 0xbf, 0xbe, 0xbc, 0xb8, 0xc3, 0xdd, 0xfa, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xcd, 0x7b, 0x61,
|
||||
0x65, 0x6c, 0x6b, 0x6a, 0x65, 0x5e, 0xa8, 0xff, 0xff, 0xff, 0xc0,
|
||||
0x78, 0xbc, 0xff, 0xff, 0xd5, 0xd5, 0xff, 0xff, 0xcf, 0xa6, 0xd9,
|
||||
0xff, 0xff, 0xfd, 0xd6, 0xb9, 0xbd, 0xbf, 0xbf, 0xc0, 0xbc, 0xbb,
|
||||
0xc8, 0xec, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xc6, 0x64, 0x5e, 0x68, 0x6a, 0x6a, 0x6b, 0x64, 0x66, 0xb2, 0xff,
|
||||
0xff, 0xfc, 0xb3, 0x73, 0x7c, 0xe4, 0xff, 0xff, 0xa4, 0xa4, 0xff,
|
||||
0xff, 0xea, 0xa6, 0xa2, 0xd1, 0xff, 0xff, 0xff, 0xdb, 0xbc, 0xbc,
|
||||
0xbf, 0xbf, 0xbf, 0xbe, 0xb9, 0xbe, 0xe9, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xf0, 0x72, 0x5f, 0x69, 0x6a, 0x69, 0x69, 0x5e,
|
||||
0x6c, 0xc4, 0xff, 0xff, 0xff, 0xae, 0x74, 0x77, 0x86, 0xf8, 0xff,
|
||||
0xee, 0x98, 0x98, 0xee, 0xff, 0xf9, 0xad, 0xa3, 0xa3, 0xce, 0xff,
|
||||
0xff, 0xff, 0xe2, 0xbe, 0xba, 0xbe, 0xbe, 0xbf, 0xbe, 0xba, 0xc5,
|
||||
0xfa, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xd3, 0x60, 0x63, 0x67,
|
||||
0x66, 0x63, 0x5f, 0x87, 0xd8, 0xff, 0xff, 0xff, 0xb7, 0x74, 0x7d,
|
||||
0x79, 0x9b, 0xfd, 0xff, 0xd0, 0x8f, 0x8f, 0xd0, 0xff, 0xfd, 0xba,
|
||||
0xa5, 0xa7, 0xa3, 0xd3, 0xff, 0xff, 0xff, 0xec, 0xc9, 0xba, 0xbc,
|
||||
0xbd, 0xbe, 0xbb, 0xbb, 0xef, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xf2, 0x7a, 0x5c, 0x62, 0x63, 0x78, 0xbd, 0xf7, 0xff, 0xff, 0xff,
|
||||
0xc6, 0x7b, 0x7d, 0x80, 0x77, 0xbb, 0xff, 0xff, 0xb7, 0x8c, 0x8c,
|
||||
0xb7, 0xff, 0xff, 0xcf, 0xa4, 0xaa, 0xa7, 0xa7, 0xdd, 0xff, 0xff,
|
||||
0xff, 0xfa, 0xe0, 0xc4, 0xbb, 0xbb, 0xb9, 0xc8, 0xfb, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xe6, 0xae, 0xab, 0xca, 0xf6, 0xff,
|
||||
0xff, 0xff, 0xff, 0xd8, 0x7e, 0x7b, 0x80, 0x7f, 0x7b, 0xdf, 0xff,
|
||||
0xef, 0xa4, 0x90, 0x90, 0xa4, 0xef, 0xff, 0xe8, 0xa6, 0xa9, 0xaa,
|
||||
0xa6, 0xab, 0xe9, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xe6, 0xda, 0xdb,
|
||||
0xf5, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf5, 0x94, 0x77, 0x81, 0x81,
|
||||
0x7a, 0x97, 0xf5, 0xff, 0xe0, 0x95, 0x94, 0x94, 0x95, 0xe0, 0xff,
|
||||
0xf7, 0xb7, 0xa6, 0xaa, 0xaa, 0xa3, 0xba, 0xfa, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xc1,
|
||||
0x76, 0x7f, 0x7f, 0x81, 0x74, 0xbf, 0xff, 0xff, 0xd5, 0x8c, 0x96,
|
||||
0x96, 0x8c, 0xd5, 0xff, 0xff, 0xd1, 0xa1, 0xab, 0xaa, 0xa9, 0xa4,
|
||||
0xd9, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf5, 0x89, 0x7a, 0x7f, 0x80, 0x7b, 0x88, 0xe6, 0xff,
|
||||
0xff, 0xc7, 0x8b, 0x96, 0x96, 0x8b, 0xc7, 0xff, 0xff, 0xec, 0xad,
|
||||
0xa7, 0xaa, 0xa9, 0xa6, 0xb2, 0xfa, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xce, 0x79, 0x7d, 0x80, 0x7e,
|
||||
0x79, 0xbb, 0xff, 0xff, 0xfe, 0xbd, 0x8d, 0x96, 0x96, 0x8d, 0xbd,
|
||||
0xfe, 0xff, 0xff, 0xce, 0xa5, 0xa9, 0xaa, 0xa7, 0xa6, 0xe1, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xb5,
|
||||
0x78, 0x7e, 0x7f, 0x75, 0x92, 0xf5, 0xff, 0xff, 0xfd, 0xbb, 0x8d,
|
||||
0x96, 0x96, 0x8d, 0xbb, 0xfd, 0xff, 0xff, 0xf6, 0xb4, 0xa3, 0xa9,
|
||||
0xa8, 0xa5, 0xcf, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xb9, 0x74, 0x79, 0x76, 0x7e, 0xda, 0xff, 0xfe,
|
||||
0xff, 0xfd, 0xbb, 0x8d, 0x96, 0x96, 0x8d, 0xbb, 0xfd, 0xff, 0xfe,
|
||||
0xff, 0xe2, 0xa7, 0xa3, 0xa6, 0xa2, 0xd1, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xeb, 0x91, 0x78, 0x8c,
|
||||
0xd5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x8b, 0x96, 0x96, 0x8b,
|
||||
0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xaf, 0xa4, 0xb3, 0xf0,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xf7, 0xe0, 0xf6, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xd1,
|
||||
0x8c, 0x97, 0x97, 0x8c, 0xd1, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xf6, 0xe6, 0xf8, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xff, 0xee, 0x9e, 0x8c, 0x8c, 0x9e, 0xee, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xd9, 0xa2, 0xa2,
|
||||
0xd9, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf4, 0xf4, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
static const unsigned char twirl_60_data[] = {
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xe9, 0xe9, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xfe, 0xfd, 0xff, 0xb4, 0x45, 0x45, 0xb4, 0xff, 0xfd, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xde, 0x3d, 0x1a, 0x1a,
|
||||
0x3d, 0xde, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xef, 0xc7, 0xeb, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xa3, 0x1a, 0x2f, 0x2f, 0x1a, 0xa3, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xee, 0xc7, 0xf0, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xdf, 0x55, 0x34, 0x4b, 0xb6, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x80, 0x18, 0x2e, 0x2e, 0x18, 0x80, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xb2, 0x2c, 0x07, 0x35, 0xda, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x97, 0x2e,
|
||||
0x37, 0x32, 0x39, 0xbe, 0xff, 0xfe, 0xff, 0xfc, 0x78, 0x1c, 0x2d,
|
||||
0x2d, 0x1c, 0x78, 0xfc, 0xff, 0xfe, 0xff, 0xba, 0x12, 0x03, 0x09,
|
||||
0x00, 0x7e, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0x94, 0x35, 0x3d, 0x3f, 0x31, 0x57, 0xeb, 0xff, 0xff,
|
||||
0xfb, 0x77, 0x1c, 0x2d, 0x2d, 0x1c, 0x77, 0xfb, 0xff, 0xff, 0xed,
|
||||
0x37, 0x02, 0x14, 0x12, 0x07, 0x78, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xbb, 0x37, 0x3b, 0x40, 0x3e,
|
||||
0x34, 0x92, 0xff, 0xff, 0xfd, 0x7c, 0x1c, 0x2d, 0x2d, 0x1c, 0x7c,
|
||||
0xfd, 0xff, 0xff, 0x83, 0x08, 0x12, 0x15, 0x0f, 0x09, 0xa4, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xf4,
|
||||
0x52, 0x37, 0x3f, 0x40, 0x39, 0x47, 0xd5, 0xff, 0xff, 0x90, 0x18,
|
||||
0x2e, 0x2e, 0x18, 0x90, 0xff, 0xff, 0xd2, 0x24, 0x0b, 0x15, 0x14,
|
||||
0x0b, 0x26, 0xec, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xa9, 0x32, 0x3f, 0x40, 0x43, 0x2d, 0x9a,
|
||||
0xff, 0xff, 0xab, 0x19, 0x2e, 0x2e, 0x19, 0xab, 0xff, 0xff, 0x8a,
|
||||
0x00, 0x18, 0x14, 0x14, 0x03, 0x8d, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf4, 0x65, 0x31,
|
||||
0x41, 0x41, 0x38, 0x5d, 0xed, 0xff, 0xc2, 0x2c, 0x29, 0x29, 0x2c,
|
||||
0xc2, 0xff, 0xec, 0x40, 0x0a, 0x17, 0x17, 0x04, 0x39, 0xed, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xe4, 0xa2, 0x9c, 0xbd, 0xf1, 0xff, 0xff,
|
||||
0xff, 0xff, 0xcd, 0x42, 0x38, 0x40, 0x3e, 0x36, 0xca, 0xff, 0xe0,
|
||||
0x49, 0x22, 0x22, 0x49, 0xe0, 0xff, 0xc4, 0x0b, 0x13, 0x16, 0x0d,
|
||||
0x12, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xa4, 0x70, 0x74, 0xd4,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x6d, 0x45, 0x4b,
|
||||
0x4c, 0x61, 0xac, 0xf1, 0xff, 0xff, 0xff, 0xb3, 0x3b, 0x3a, 0x40,
|
||||
0x33, 0x93, 0xff, 0xff, 0x70, 0x1a, 0x1a, 0x70, 0xff, 0xff, 0x81,
|
||||
0x04, 0x15, 0x0f, 0x0c, 0x97, 0xff, 0xff, 0xff, 0xf1, 0x8e, 0x19,
|
||||
0x00, 0x00, 0x00, 0x1c, 0xe9, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xd6, 0x4b, 0x4c, 0x52, 0x50, 0x4e, 0x47, 0x6f, 0xcc, 0xff, 0xff,
|
||||
0xff, 0x9c, 0x31, 0x3b, 0x36, 0x64, 0xfb, 0xff, 0xa0, 0x20, 0x20,
|
||||
0xa0, 0xff, 0xfc, 0x47, 0x09, 0x10, 0x00, 0x7b, 0xff, 0xff, 0xff,
|
||||
0xbc, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xf3, 0x65, 0x47, 0x54, 0x55, 0x54, 0x54,
|
||||
0x49, 0x53, 0xb3, 0xff, 0xff, 0xff, 0x90, 0x30, 0x32, 0x47, 0xf1,
|
||||
0xff, 0xde, 0x31, 0x31, 0xde, 0xff, 0xf2, 0x21, 0x05, 0x00, 0x6b,
|
||||
0xff, 0xff, 0xff, 0x99, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0f, 0xe5, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xc7, 0x53,
|
||||
0x47, 0x53, 0x55, 0x54, 0x56, 0x4f, 0x4e, 0x9f, 0xff, 0xff, 0xff,
|
||||
0x97, 0x30, 0x38, 0xd0, 0xff, 0xff, 0x4a, 0x4a, 0xff, 0xff, 0xcc,
|
||||
0x0e, 0x00, 0x74, 0xfa, 0xff, 0xff, 0x7b, 0x00, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x9e, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xcd, 0x6e, 0x4b, 0x4e, 0x57, 0x56, 0x55, 0x50,
|
||||
0x46, 0x93, 0xfc, 0xff, 0xff, 0xaa, 0x38, 0x93, 0xff, 0xff, 0xac,
|
||||
0xac, 0xff, 0xff, 0x83, 0x06, 0x8b, 0xff, 0xff, 0xff, 0x6a, 0x00,
|
||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x1e, 0xa8, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xf2, 0xa4, 0x60,
|
||||
0x44, 0x4d, 0x53, 0x55, 0x51, 0x45, 0x9a, 0xff, 0xff, 0xff, 0xb1,
|
||||
0xa8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x92, 0x95, 0xff, 0xff,
|
||||
0xff, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x6d, 0xe4,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xde, 0xa9, 0x74, 0x4e, 0x49, 0x4c, 0x49, 0x43,
|
||||
0xab, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfc, 0xff, 0xff, 0xff, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27,
|
||||
0x77, 0xc7, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xd4,
|
||||
0xa4, 0x79, 0x5d, 0x4f, 0x4b, 0xb2, 0xff, 0xfc, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfa, 0xff, 0x98, 0x00, 0x00, 0x0a,
|
||||
0x2f, 0x6e, 0xb9, 0xe7, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfd, 0xfc, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf5, 0xda, 0xa5, 0xb0, 0xfd,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0x8b, 0x70, 0xc0, 0xee, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xe8, 0xbe,
|
||||
0xa5, 0xa0, 0xa0, 0xa3, 0xb1, 0xc4, 0xd4, 0xe9, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
|
||||
0xf9, 0xf6, 0xf4, 0xf2, 0xf1, 0xf1, 0xf2, 0xf6, 0xfb, 0xff, 0xff,
|
||||
0xff, 0xca, 0x76, 0x5e, 0x5d, 0x5f, 0x60, 0x5f, 0x5d, 0x5d, 0x6b,
|
||||
0x7f, 0x9b, 0xbd, 0xe7, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfc, 0xf6, 0xf1, 0xed, 0xea, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8,
|
||||
0xe8, 0xeb, 0xf7, 0xff, 0xef, 0x7c, 0x5e, 0x6d, 0x6c, 0x6b, 0x6b,
|
||||
0x6b, 0x6c, 0x6c, 0x69, 0x63, 0x5e, 0x62, 0x6e, 0x7f, 0xc4, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf6, 0xed, 0xea, 0xe9, 0xe8, 0xe9, 0xe9, 0xea, 0xea,
|
||||
0xea, 0xea, 0xea, 0xea, 0xea, 0xe8, 0xec, 0xfd, 0xef, 0x7c, 0x5e,
|
||||
0x6d, 0x6c, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, 0x69, 0x63, 0x5e, 0x62,
|
||||
0x6e, 0x7f, 0xc4, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xf6, 0xed, 0xea, 0xe9, 0xe8,
|
||||
0xe9, 0xe9, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xe8, 0xec,
|
||||
0xfd, 0xff, 0xca, 0x76, 0x5e, 0x5d, 0x5f, 0x60, 0x5f, 0x5d, 0x5d,
|
||||
0x6b, 0x7f, 0x9b, 0xbd, 0xe7, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfc, 0xf6, 0xf1, 0xed, 0xea, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8,
|
||||
0xe8, 0xe8, 0xeb, 0xf7, 0xff, 0xff, 0xff, 0xe8, 0xbe, 0xa5, 0xa0,
|
||||
0xa0, 0xa3, 0xb1, 0xc4, 0xd4, 0xe9, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xf9, 0xf6,
|
||||
0xf4, 0xf2, 0xf1, 0xf1, 0xf2, 0xf6, 0xfb, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfd, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfc, 0xf6, 0xdf, 0xb7, 0xc5, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xeb, 0xe8, 0xf6, 0xfd, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xf3, 0xdc, 0xb7, 0x98, 0x85, 0x7a, 0x7a, 0xcb, 0xff, 0xfd,
|
||||
0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xec,
|
||||
0xd2, 0xd3, 0xd7, 0xde, 0xe8, 0xf4, 0xfb, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xe3, 0xbc, 0x94, 0x7a, 0x77, 0x79, 0x76, 0x75,
|
||||
0xc7, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xea, 0xd0, 0xd2, 0xd2, 0xd2, 0xd3, 0xdc,
|
||||
0xea, 0xf7, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xf1, 0xb6, 0x85, 0x73, 0x7a, 0x7f,
|
||||
0x80, 0x7c, 0x75, 0xba, 0xff, 0xff, 0xff, 0xcf, 0xcd, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xe2, 0xe5, 0xff, 0xff, 0xff, 0xe6, 0xd1,
|
||||
0xd4, 0xd5, 0xd4, 0xd3, 0xd1, 0xd8, 0xe8, 0xfb, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xd4, 0x8f, 0x78,
|
||||
0x7b, 0x82, 0x80, 0x80, 0x7c, 0x76, 0xb5, 0xff, 0xff, 0xff, 0xcb,
|
||||
0x8e, 0xc7, 0xff, 0xff, 0xde, 0xde, 0xff, 0xff, 0xda, 0xbc, 0xe3,
|
||||
0xff, 0xff, 0xfe, 0xe4, 0xd1, 0xd3, 0xd5, 0xd5, 0xd5, 0xd3, 0xd2,
|
||||
0xdb, 0xf2, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xcf, 0x7b, 0x76, 0x7e, 0x80, 0x80, 0x81, 0x7b, 0x7d, 0xbd, 0xff,
|
||||
0xff, 0xfd, 0xc0, 0x8a, 0x91, 0xe8, 0xff, 0xff, 0xb6, 0xb6, 0xff,
|
||||
0xff, 0xef, 0xbc, 0xb9, 0xdc, 0xff, 0xff, 0xff, 0xe7, 0xd3, 0xd3,
|
||||
0xd5, 0xd4, 0xd5, 0xd4, 0xd1, 0xd4, 0xf1, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xf2, 0x87, 0x76, 0x7f, 0x80, 0x7f, 0x7f, 0x76,
|
||||
0x81, 0xcc, 0xff, 0xff, 0xff, 0xbc, 0x8b, 0x8d, 0x9a, 0xf9, 0xff,
|
||||
0xf1, 0xac, 0xac, 0xf1, 0xff, 0xfa, 0xc1, 0xba, 0xb9, 0xda, 0xff,
|
||||
0xff, 0xff, 0xec, 0xd4, 0xd2, 0xd4, 0xd4, 0xd5, 0xd4, 0xd1, 0xd9,
|
||||
0xfc, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xda, 0x78, 0x7a, 0x7e,
|
||||
0x7c, 0x7a, 0x76, 0x98, 0xdd, 0xff, 0xff, 0xff, 0xc3, 0x8b, 0x92,
|
||||
0x8f, 0xab, 0xfd, 0xff, 0xd9, 0xa5, 0xa5, 0xd9, 0xff, 0xfd, 0xcb,
|
||||
0xbb, 0xbd, 0xba, 0xde, 0xff, 0xff, 0xff, 0xf2, 0xdb, 0xd1, 0xd3,
|
||||
0xd4, 0xd4, 0xd2, 0xd2, 0xf5, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xf4, 0x8e, 0x74, 0x79, 0x7a, 0x8c, 0xc7, 0xf8, 0xff, 0xff, 0xff,
|
||||
0xcf, 0x90, 0x92, 0x95, 0x8d, 0xc6, 0xff, 0xff, 0xc6, 0xa3, 0xa3,
|
||||
0xc6, 0xff, 0xff, 0xda, 0xba, 0xbf, 0xbd, 0xbd, 0xe5, 0xff, 0xff,
|
||||
0xff, 0xfb, 0xea, 0xd8, 0xd2, 0xd2, 0xd1, 0xda, 0xfc, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xea, 0xba, 0xb8, 0xd1, 0xf7, 0xff,
|
||||
0xff, 0xff, 0xff, 0xdf, 0x93, 0x91, 0x95, 0x94, 0x91, 0xe5, 0xff,
|
||||
0xf3, 0xb6, 0xa6, 0xa6, 0xb6, 0xf3, 0xff, 0xed, 0xbb, 0xbe, 0xbf,
|
||||
0xbc, 0xbf, 0xee, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xee, 0xe6, 0xe8,
|
||||
0xf8, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf6, 0xa5, 0x8d, 0x96, 0x95,
|
||||
0x90, 0xa8, 0xf6, 0xff, 0xe6, 0xaa, 0xa9, 0xa9, 0xaa, 0xe6, 0xff,
|
||||
0xf9, 0xc9, 0xbc, 0xbf, 0xbf, 0xba, 0xcb, 0xfb, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xcc,
|
||||
0x8d, 0x94, 0x94, 0x96, 0x8a, 0xc9, 0xff, 0xff, 0xdd, 0xa2, 0xab,
|
||||
0xab, 0xa2, 0xdd, 0xff, 0xff, 0xdd, 0xb9, 0xc0, 0xbf, 0xbe, 0xba,
|
||||
0xe2, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf6, 0x9c, 0x90, 0x94, 0x95, 0x90, 0x9b, 0xea, 0xff,
|
||||
0xff, 0xd3, 0xa2, 0xab, 0xab, 0xa2, 0xd3, 0xff, 0xff, 0xf1, 0xc1,
|
||||
0xbc, 0xbf, 0xbe, 0xbc, 0xc5, 0xfb, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xd6, 0x8f, 0x92, 0x95, 0x93,
|
||||
0x8f, 0xc6, 0xff, 0xff, 0xfe, 0xca, 0xa4, 0xab, 0xab, 0xa4, 0xca,
|
||||
0xfe, 0xff, 0xff, 0xda, 0xbb, 0xbe, 0xbf, 0xbd, 0xbc, 0xe8, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xc1,
|
||||
0x8f, 0x93, 0x94, 0x8c, 0xa4, 0xf7, 0xff, 0xff, 0xfd, 0xc8, 0xa4,
|
||||
0xaa, 0xaa, 0xa4, 0xc8, 0xfd, 0xff, 0xff, 0xf8, 0xc6, 0xba, 0xbe,
|
||||
0xbe, 0xbb, 0xdb, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xc5, 0x8b, 0x8f, 0x8c, 0x93, 0xe0, 0xff, 0xfe,
|
||||
0xff, 0xfd, 0xc9, 0xa4, 0xab, 0xab, 0xa4, 0xc9, 0xfd, 0xff, 0xfe,
|
||||
0xff, 0xe9, 0xbc, 0xba, 0xbb, 0xb9, 0xdc, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xee, 0xa3, 0x8f, 0x9f,
|
||||
0xdc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xa2, 0xab, 0xab, 0xa2,
|
||||
0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xc3, 0xbb, 0xc6, 0xf4,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xf8, 0xe5, 0xf7, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xda,
|
||||
0xa3, 0xab, 0xab, 0xa3, 0xda, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xf8, 0xec, 0xf9, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xff, 0xf1, 0xb1, 0xa3, 0xa3, 0xb1, 0xf1, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xe1, 0xb4, 0xb4,
|
||||
0xe1, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xf6, 0xf6, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
// Also, the "failed" icon.
|
||||
const unsigned char failed_data[] = {
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfb, 0xfb, 0xfe, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfd, 0xff, 0xfa, 0x91, 0x93, 0xfd, 0xff, 0xfd, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xfe, 0xfc, 0xff, 0x8c, 0x00, 0x00, 0x8f, 0xff,
|
||||
0xfc, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xcf, 0x0f,
|
||||
0x00, 0x00, 0x0f, 0xcf, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xff, 0x51, 0x00, 0x01, 0x01, 0x00, 0x51, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xa4, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xef, 0x1e, 0x00,
|
||||
0x00, 0x55, 0x54, 0x00, 0x00, 0x1f, 0xf0, 0xff, 0xfe, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xff, 0x6a, 0x00, 0x00, 0x1c, 0xbf, 0xbe, 0x1c, 0x00, 0x00, 0x68,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xfe, 0xfb, 0xff, 0xbb, 0x03, 0x00, 0x00, 0x7d, 0xd2, 0xd2,
|
||||
0x7e, 0x00, 0x00, 0x03, 0xbc, 0xff, 0xfb, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xf7, 0x3c, 0x00, 0x00,
|
||||
0x3f, 0xc6, 0xc4, 0xc4, 0xc6, 0x3e, 0x00, 0x00, 0x3d, 0xf8, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff,
|
||||
0x8b, 0x00, 0x00, 0x0c, 0xa1, 0xcd, 0xc0, 0xc0, 0xcd, 0xa1, 0x0c,
|
||||
0x00, 0x00, 0x89, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xcf, 0x09, 0x00, 0x00, 0x6c, 0xd4, 0xc2, 0xc0,
|
||||
0xc0, 0xc2, 0xd4, 0x6d, 0x00, 0x00, 0x09, 0xd0, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x4a, 0x00, 0x00, 0x29,
|
||||
0xc3, 0xca, 0xc7, 0xd0, 0xd0, 0xc7, 0xca, 0xc3, 0x29, 0x00, 0x00,
|
||||
0x4a, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xa5,
|
||||
0x00, 0x00, 0x00, 0x90, 0xd1, 0xc7, 0xb2, 0x52, 0x52, 0xb2, 0xc7,
|
||||
0xd1, 0x91, 0x00, 0x00, 0x00, 0xa6, 0xff, 0xfe, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfc, 0xff, 0xeb, 0x25, 0x00, 0x00, 0x53, 0xc9, 0xc4, 0xd2, 0x6b,
|
||||
0x00, 0x00, 0x6b, 0xd2, 0xc4, 0xc9, 0x53, 0x00, 0x00, 0x25, 0xec,
|
||||
0xff, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0x69, 0x00, 0x00, 0x20, 0xb3,
|
||||
0xcb, 0xbe, 0xd5, 0x58, 0x00, 0x00, 0x58, 0xd5, 0xbe, 0xcb, 0xb3,
|
||||
0x21, 0x00, 0x00, 0x69, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xb7, 0x01,
|
||||
0x00, 0x00, 0x7e, 0xd3, 0xc0, 0xbf, 0xd4, 0x5d, 0x00, 0x00, 0x5d,
|
||||
0xd4, 0xbf, 0xc0, 0xd3, 0x7f, 0x00, 0x00, 0x01, 0xb8, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xf5, 0x36, 0x00, 0x00, 0x3f, 0xc9, 0xc5, 0xc2, 0xbf, 0xd4,
|
||||
0x5d, 0x00, 0x00, 0x5d, 0xd4, 0xbf, 0xc2, 0xc5, 0xca, 0x3f, 0x00,
|
||||
0x00, 0x37, 0xf6, 0xff, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0x87, 0x00, 0x00, 0x07, 0xa6, 0xcd,
|
||||
0xc2, 0xc3, 0xbf, 0xd4, 0x5d, 0x00, 0x00, 0x5d, 0xd4, 0xbf, 0xc3,
|
||||
0xc2, 0xcd, 0xa6, 0x07, 0x00, 0x00, 0x85, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xff, 0xd4, 0x06, 0x00,
|
||||
0x00, 0x6c, 0xd3, 0xc5, 0xc2, 0xc3, 0xbf, 0xd4, 0x5d, 0x00, 0x00,
|
||||
0x5d, 0xd4, 0xbf, 0xc2, 0xc2, 0xc5, 0xd3, 0x6d, 0x00, 0x00, 0x06,
|
||||
0xd5, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0x4e, 0x00, 0x00, 0x2e, 0xbe, 0xcd, 0xc2, 0xc2, 0xc2, 0xbf,
|
||||
0xd4, 0x5d, 0x00, 0x00, 0x5d, 0xd4, 0xbf, 0xc3, 0xc2, 0xc2, 0xcd,
|
||||
0xbe, 0x2e, 0x00, 0x00, 0x4f, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xfe, 0xfb, 0xff, 0xa2, 0x00, 0x00, 0x02, 0x8f, 0xcf, 0xc3,
|
||||
0xc2, 0xc2, 0xc2, 0xbf, 0xd4, 0x5d, 0x00, 0x00, 0x5d, 0xd4, 0xbf,
|
||||
0xc3, 0xc2, 0xc2, 0xc3, 0xcf, 0x8f, 0x02, 0x00, 0x00, 0xa1, 0xff,
|
||||
0xfb, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xfe, 0xfd, 0xff, 0xe6, 0x27, 0x00, 0x00,
|
||||
0x53, 0xcb, 0xc2, 0xc2, 0xc3, 0xc2, 0xc3, 0xbf, 0xd4, 0x5c, 0x00,
|
||||
0x00, 0x5c, 0xd4, 0xbf, 0xc3, 0xc2, 0xc3, 0xc2, 0xc1, 0xcc, 0x54,
|
||||
0x00, 0x00, 0x27, 0xe8, 0xff, 0xfd, 0xfe, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0x66, 0x00, 0x00, 0x20, 0xb7, 0xcc, 0xc1, 0xc3, 0xc2, 0xc2, 0xc3,
|
||||
0xbf, 0xd4, 0x59, 0x00, 0x00, 0x59, 0xd4, 0xbf, 0xc3, 0xc2, 0xc2,
|
||||
0xc3, 0xc1, 0xcc, 0xb7, 0x20, 0x00, 0x00, 0x66, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xfe, 0xff, 0xff, 0xb7, 0x00, 0x00, 0x00, 0x83, 0xd3, 0xc2, 0xc3,
|
||||
0xc3, 0xc2, 0xc2, 0xc2, 0xc0, 0xce, 0x86, 0x01, 0x01, 0x86, 0xce,
|
||||
0xc0, 0xc3, 0xc2, 0xc2, 0xc2, 0xc3, 0xc2, 0xd3, 0x84, 0x00, 0x00,
|
||||
0x00, 0xb8, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0x30, 0x00, 0x00, 0x3e,
|
||||
0xca, 0xc8, 0xc2, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3,
|
||||
0x90, 0x90, 0xc3, 0xc3, 0xc2, 0xc3, 0xc2, 0xc2, 0xc2, 0xc3, 0xc2,
|
||||
0xc8, 0xca, 0x3d, 0x00, 0x00, 0x31, 0xfd, 0xff, 0xfe, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8a,
|
||||
0x00, 0x00, 0x0a, 0xa2, 0xcf, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2,
|
||||
0xc3, 0xc3, 0xc2, 0xc3, 0xd3, 0xd3, 0xc3, 0xc2, 0xc3, 0xc3, 0xc2,
|
||||
0xc2, 0xc3, 0xc3, 0xc3, 0xc3, 0xcf, 0xa2, 0x0a, 0x00, 0x00, 0x8a,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff,
|
||||
0xfc, 0xff, 0xcf, 0x0e, 0x00, 0x00, 0x6b, 0xd0, 0xc5, 0xc2, 0xc2,
|
||||
0xc3, 0xc3, 0xc2, 0xc2, 0xc3, 0xc3, 0xc1, 0xc9, 0xc5, 0xc5, 0xc9,
|
||||
0xc1, 0xc3, 0xc3, 0xc2, 0xc2, 0xc3, 0xc3, 0xc2, 0xc2, 0xc5, 0xd1,
|
||||
0x6e, 0x00, 0x00, 0x0e, 0xcf, 0xff, 0xfb, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0x4e, 0x00, 0x00, 0x31, 0xbf,
|
||||
0xc8, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc2, 0xc2, 0xc2, 0xc1, 0xc9,
|
||||
0xab, 0x52, 0x52, 0xab, 0xc9, 0xc1, 0xc3, 0xc2, 0xc2, 0xc2, 0xc3,
|
||||
0xc2, 0xc2, 0xc2, 0xc8, 0xc0, 0x30, 0x00, 0x00, 0x4f, 0xff, 0xff,
|
||||
0xfe, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff, 0xa0, 0x00,
|
||||
0x00, 0x04, 0x93, 0xd1, 0xc0, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc2,
|
||||
0xc2, 0xc2, 0xc0, 0xcf, 0x4c, 0x00, 0x00, 0x4c, 0xcf, 0xc0, 0xc3,
|
||||
0xc2, 0xc2, 0xc2, 0xc3, 0xc2, 0xc2, 0xc2, 0xc0, 0xd1, 0x92, 0x04,
|
||||
0x00, 0x00, 0x9e, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xe5, 0x1e, 0x00, 0x00, 0x54, 0xcd, 0xc3, 0xc3, 0xc3, 0xc2,
|
||||
0xc2, 0xc3, 0xc3, 0xc2, 0xc2, 0xc3, 0xc0, 0xd0, 0x4e, 0x00, 0x00,
|
||||
0x4e, 0xd0, 0xc0, 0xc3, 0xc2, 0xc2, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2,
|
||||
0xc3, 0xc3, 0xce, 0x56, 0x00, 0x00, 0x20, 0xe6, 0xff, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0x60, 0x00, 0x00, 0x1b, 0xba, 0xcc,
|
||||
0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1,
|
||||
0xc7, 0xae, 0x4c, 0x4c, 0xae, 0xc7, 0xc1, 0xc2, 0xc2, 0xc2, 0xc2,
|
||||
0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xcc, 0xbb, 0x19, 0x00, 0x00,
|
||||
0x61, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xbd, 0x00, 0x00,
|
||||
0x00, 0x85, 0xd6, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
|
||||
0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xcb, 0xd2, 0xd2, 0xcb, 0xc5, 0xc6,
|
||||
0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc7,
|
||||
0xd6, 0x84, 0x00, 0x00, 0x00, 0xbb, 0xff, 0xfe, 0xff, 0xfd, 0xff,
|
||||
0xf7, 0x38, 0x00, 0x00, 0x42, 0xbf, 0xc6, 0xbe, 0xbe, 0xbe, 0xbe,
|
||||
0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf,
|
||||
0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
|
||||
0xbe, 0xbe, 0xbe, 0xbe, 0xc6, 0xc0, 0x42, 0x00, 0x00, 0x38, 0xf9,
|
||||
0xff, 0xfd, 0xfc, 0xff, 0x83, 0x00, 0x00, 0x00, 0x16, 0x24, 0x20,
|
||||
0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
|
||||
0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
|
||||
0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x20, 0x24, 0x16,
|
||||
0x00, 0x00, 0x00, 0x82, 0xff, 0xfd, 0xff, 0xfa, 0x34, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0xf7, 0xff, 0xfe,
|
||||
0xff, 0x86, 0x12, 0x11, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
|
||||
0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
|
||||
0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
|
||||
0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x12, 0x18,
|
||||
0x8a, 0xff, 0xfe, 0xfe, 0xff, 0xfd, 0xd2, 0xc4, 0xc8, 0xc8, 0xc8,
|
||||
0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8,
|
||||
0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8,
|
||||
0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8,
|
||||
0xc8, 0xc8, 0xc5, 0xd8, 0xff, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe,
|
||||
0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe,
|
||||
0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff,
|
||||
0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xfe,
|
||||
0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff,
|
||||
0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
|
||||
0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
|
||||
0xff, 0xfe, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
static const unsigned char * const twirl_data[4] = {
|
||||
twirl_0_data,
|
||||
twirl_30_data,
|
||||
twirl_60_data,
|
||||
failed_data
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fills the indicated data array with a string of twirl_width * twirl_height
|
||||
* * 3 bytes, representing the RGB pixel values of the twirl_width x
|
||||
* twirl_height image at frame number step of twirl_num_steps frames. The
|
||||
* specified fg and bg colors are applied to the array appropriately.
|
||||
*
|
||||
* Returns true on success, false on failure. On false, the array is
|
||||
* initialized with zero.
|
||||
*
|
||||
* You must pass data_length = twirl_width * twirl_height * 3; this value is
|
||||
* passed as a sanity check on array size. You should pass step so that 0 <=
|
||||
* step < twirl_num_steps + 1. (The special value of step == twirl_num_steps
|
||||
* returns the "failed" icon.)
|
||||
*/
|
||||
bool
|
||||
get_twirl_data(unsigned char data[], size_t data_length, int step,
|
||||
int fg_r, int fg_g, int fg_b,
|
||||
int bg_r, int bg_g, int bg_b) {
|
||||
|
||||
if (step < 0 || step >= twirl_num_steps + 1) {
|
||||
memset(data, 0, data_length);
|
||||
return false;
|
||||
}
|
||||
if (data_length != twirl_width * twirl_height) {
|
||||
memset(data, 0, data_length);
|
||||
return false;
|
||||
}
|
||||
|
||||
twirl_flip &flip = twirl_flip_table[step];
|
||||
const unsigned char *in_data = twirl_data[flip._index];
|
||||
|
||||
for (int yi = 0; yi < twirl_height; ++yi) {
|
||||
const unsigned char *sp = &in_data[yi * twirl_width];
|
||||
for (int xi = 0; xi < twirl_width; ++xi) {
|
||||
int xo = xi;
|
||||
int yo = yi;
|
||||
if (flip._flip_x) {
|
||||
xo = twirl_width - 1 - xo;
|
||||
}
|
||||
if (flip._flip_y) {
|
||||
yo = twirl_width - 1 - yo;
|
||||
}
|
||||
if (flip._exchange) {
|
||||
int t = xo;
|
||||
xo = yo;
|
||||
yo = t;
|
||||
}
|
||||
unsigned char *dp = &data[yo * twirl_width * 3];
|
||||
dp += 3 * xo;
|
||||
|
||||
double t = (double)(*sp) / 255.0;
|
||||
++sp;
|
||||
|
||||
dp[0] = (unsigned char)(fg_r + t * (bg_r - fg_r));
|
||||
dp[1] = (unsigned char)(fg_g + t * (bg_g - fg_g));
|
||||
dp[2] = (unsigned char)(fg_b + t * (bg_b - fg_b));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file get_twirl_data.h
|
||||
* @author drose
|
||||
* @date 2011-08-24
|
||||
*/
|
||||
|
||||
#ifndef GET_TWIRL_DATA_H
|
||||
#define GET_TWIRL_DATA_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
static const int twirl_width = 48;
|
||||
static const int twirl_height = 48;
|
||||
static const int twirl_num_steps = 12;
|
||||
|
||||
bool get_twirl_data(unsigned char data[], size_t data_length, int step,
|
||||
int fg_r, int fg_g, int fg_b,
|
||||
int bg_r, int bg_g, int bg_b);
|
||||
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file handleStream.I
|
||||
* @author drose
|
||||
* @date 2009-06-05
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inline HandleStream::
|
||||
HandleStream() : std::iostream(&_buf) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inline HandleStream::
|
||||
~HandleStream() {
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open the given handle for input. The stream may not be
|
||||
* simultaneously open for input and output.
|
||||
*/
|
||||
inline void HandleStream::
|
||||
open_read(FHandle handle) {
|
||||
clear((std::ios::iostatetate)0);
|
||||
_buf.open_read(handle);
|
||||
if (!_buf.is_open_read()) {
|
||||
clear(std::ios::failbit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open the given handle for output. The stream may not be
|
||||
* simultaneously open for input and output.
|
||||
*/
|
||||
inline void HandleStream::
|
||||
open_write(FHandle handle) {
|
||||
clear((std::ios::iostatetate)0);
|
||||
_buf.open_write(handle);
|
||||
if (!_buf.is_open_write()) {
|
||||
clear(std::ios::failbit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inline void HandleStream::
|
||||
close() {
|
||||
_buf.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying handle, *without* attempting to flush the stream.
|
||||
*/
|
||||
inline void HandleStream::
|
||||
close_handle() {
|
||||
_buf.close_handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the handle that was passed to open_read() or open_write().
|
||||
*/
|
||||
inline FHandle HandleStream::
|
||||
get_handle() const {
|
||||
return _buf.get_handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is data in the stream's "get" buffer, meaning that at
|
||||
* least one character can be extracted from the stream without making an OS
|
||||
* read() call. Returns false if the get buffer is empty, meaning the next
|
||||
* read call will hit the OS.
|
||||
*/
|
||||
inline bool HandleStream::
|
||||
has_gdata() const {
|
||||
return _buf.has_gdata();
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file handleStream.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-05
|
||||
*/
|
||||
|
||||
#include "handleStream.h"
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file handleStream.h
|
||||
* @author drose
|
||||
* @date 2009-06-05
|
||||
*/
|
||||
|
||||
#ifndef HANDLESTREAM_H
|
||||
#define HANDLESTREAM_H
|
||||
|
||||
#include "handleStreamBuf.h"
|
||||
|
||||
/**
|
||||
* Implements a C++ stream object suitable for reading from and writing to
|
||||
* Windows' HANDLE objects, or Posix file descriptors. This is necessary to
|
||||
* map low-level pipes into an iostream for tinyxml.
|
||||
*/
|
||||
class HandleStream : public std::iostream {
|
||||
public:
|
||||
inline HandleStream();
|
||||
inline ~HandleStream();
|
||||
|
||||
inline void open_read(FHandle handle);
|
||||
inline void open_write(FHandle handle);
|
||||
inline void close();
|
||||
inline void close_handle();
|
||||
|
||||
inline FHandle get_handle() const;
|
||||
inline bool has_gdata() const;
|
||||
|
||||
private:
|
||||
HandleStreamBuf _buf;
|
||||
};
|
||||
|
||||
#include "handleStream.I"
|
||||
|
||||
#endif
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file handleStreamBuf.I
|
||||
* @author drose
|
||||
* @date 2009-06-05
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the handle that was passed to open_read() or open_write().
|
||||
*/
|
||||
inline FHandle HandleStreamBuf::
|
||||
get_handle() const {
|
||||
return _handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is data in the stream's "get" buffer, meaning that at
|
||||
* least one character can be extracted from the stream without making an OS
|
||||
* read() call. Returns false if the get buffer is empty, meaning the next
|
||||
* read call will hit the OS.
|
||||
*/
|
||||
inline bool HandleStreamBuf::
|
||||
has_gdata() const {
|
||||
return (egptr() != gptr());
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file handleStreamBuf.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-05
|
||||
*/
|
||||
|
||||
#include "handleStreamBuf.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#endif // _WIN32
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__FreeBSD__)
|
||||
#include <libio.h>
|
||||
#endif // !_WIN32 && !__APPLE__ && !__FreeBSD__
|
||||
|
||||
using std::cerr;
|
||||
using std::dec;
|
||||
using std::hex;
|
||||
|
||||
static const size_t handle_buffer_size = 4096;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
HandleStreamBuf::
|
||||
HandleStreamBuf() {
|
||||
_is_open_read = false;
|
||||
_is_open_write = false;
|
||||
|
||||
_handle = invalid_fhandle;
|
||||
|
||||
INIT_LOCK(_lock);
|
||||
|
||||
_buffer = new char[handle_buffer_size];
|
||||
char *ebuf = _buffer + handle_buffer_size;
|
||||
setg(_buffer, ebuf, ebuf);
|
||||
setp(_buffer, ebuf);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
HandleStreamBuf::
|
||||
~HandleStreamBuf() {
|
||||
close();
|
||||
|
||||
delete[] _buffer;
|
||||
|
||||
DESTROY_LOCK(_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open the given handle for input. The stream may not be
|
||||
* simultaneously open for input and output.
|
||||
*/
|
||||
void HandleStreamBuf::
|
||||
open_read(FHandle handle) {
|
||||
close();
|
||||
|
||||
_handle = handle;
|
||||
_is_open_read = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open the given handle for output. The stream may not be
|
||||
* simultaneously open for input and output.
|
||||
*/
|
||||
void HandleStreamBuf::
|
||||
open_write(FHandle handle) {
|
||||
close();
|
||||
|
||||
_handle = handle;
|
||||
_is_open_write = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the file is open for input, false otherwise.
|
||||
*/
|
||||
bool HandleStreamBuf::
|
||||
is_open_read() const {
|
||||
return _is_open_read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the file is open for output, false otherwise.
|
||||
*/
|
||||
bool HandleStreamBuf::
|
||||
is_open_write() const {
|
||||
return _is_open_write;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the buffer and closes the file.
|
||||
*/
|
||||
void HandleStreamBuf::
|
||||
close() {
|
||||
// Make sure the write buffer is flushed.
|
||||
sync();
|
||||
|
||||
close_handle();
|
||||
|
||||
pbump(pbase() - pptr());
|
||||
gbump(egptr() - gptr());
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying handle, *without* attempting to flush the stream.
|
||||
*/
|
||||
void HandleStreamBuf::
|
||||
close_handle() {
|
||||
#ifdef _WIN32
|
||||
if (_handle != nullptr) {
|
||||
CloseHandle(_handle);
|
||||
}
|
||||
_handle = nullptr;
|
||||
#else
|
||||
if (_handle != -1) {
|
||||
::close(_handle);
|
||||
}
|
||||
_handle = -1;
|
||||
#endif // _WIN32
|
||||
|
||||
_is_open_read = false;
|
||||
_is_open_write = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system ostream implementation when its internal buffer is
|
||||
* filled, plus one character.
|
||||
*/
|
||||
int HandleStreamBuf::
|
||||
overflow(int ch) {
|
||||
ACQUIRE_LOCK(_lock);
|
||||
|
||||
bool okflag = true;
|
||||
|
||||
assert(pptr() >= pbase());
|
||||
size_t n = pptr() - pbase();
|
||||
if (n != 0) {
|
||||
size_t wrote = write_chars(pbase(), n);
|
||||
assert(wrote <= n);
|
||||
pbump(-(int)wrote);
|
||||
if (wrote != n) {
|
||||
okflag = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (okflag && ch != EOF) {
|
||||
if (pptr() != epptr()) {
|
||||
// Store the extra character back in the buffer.
|
||||
*(pptr()) = ch;
|
||||
pbump(1);
|
||||
} else {
|
||||
// No room to store ch.
|
||||
okflag = false;
|
||||
}
|
||||
}
|
||||
|
||||
RELEASE_LOCK(_lock);
|
||||
|
||||
if (!okflag) {
|
||||
return EOF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system iostream implementation to implement a flush
|
||||
* operation.
|
||||
*/
|
||||
int HandleStreamBuf::
|
||||
sync() {
|
||||
ACQUIRE_LOCK(_lock);
|
||||
assert(pptr() >= pbase());
|
||||
size_t n = pptr() - pbase();
|
||||
|
||||
size_t wrote = write_chars(pbase(), n);
|
||||
assert(wrote <= n);
|
||||
pbump(-(int)wrote);
|
||||
|
||||
RELEASE_LOCK(_lock);
|
||||
|
||||
if (n != wrote) {
|
||||
return EOF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system istream implementation when its internal buffer needs
|
||||
* more characters.
|
||||
*/
|
||||
int HandleStreamBuf::
|
||||
underflow() {
|
||||
ACQUIRE_LOCK(_lock);
|
||||
// Sometimes underflow() is called even if the buffer is not empty.
|
||||
if (gptr() >= egptr()) {
|
||||
// Mark the buffer filled (with buffer_size bytes).
|
||||
size_t buffer_size = egptr() - eback();
|
||||
gbump(-(int)buffer_size);
|
||||
|
||||
size_t num_bytes = buffer_size;
|
||||
size_t read_count = read_chars(gptr(), buffer_size);
|
||||
|
||||
if (read_count != num_bytes) {
|
||||
// Oops, we didn't read what we thought we would.
|
||||
if (read_count == 0) {
|
||||
gbump(num_bytes);
|
||||
RELEASE_LOCK(_lock);
|
||||
return EOF;
|
||||
}
|
||||
|
||||
// Slide what we did read to the top of the buffer.
|
||||
assert(read_count < num_bytes);
|
||||
size_t delta = num_bytes - read_count;
|
||||
memmove(gptr() + delta, gptr(), read_count);
|
||||
gbump(delta);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char next = *gptr();
|
||||
RELEASE_LOCK(_lock);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to extract the indicated number of characters from the current
|
||||
* file position. Returns the number of characters extracted.
|
||||
*/
|
||||
size_t HandleStreamBuf::
|
||||
read_chars(char *start, size_t length) {
|
||||
if (length == 0 || !_is_open_read) {
|
||||
// Trivial no-op.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure the write buffer is flushed.
|
||||
sync();
|
||||
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows case.
|
||||
DWORD bytes_read = 0;
|
||||
BOOL success = ReadFile(_handle, start, length, &bytes_read, nullptr);
|
||||
if (!success) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_HANDLE_EOF && error != ERROR_BROKEN_PIPE) {
|
||||
cerr << "Error reading " << length
|
||||
<< " bytes, windows error code 0x" << hex
|
||||
<< error << dec << ".\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
length = bytes_read;
|
||||
|
||||
#else
|
||||
// Posix case.
|
||||
ssize_t result = ::read(_handle, start, length);
|
||||
if (result < 0) {
|
||||
cerr << "Error reading " << length << " bytes\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
length = result;
|
||||
#endif // _WIN32
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the indicated stream of characters to the current file position.
|
||||
*/
|
||||
size_t HandleStreamBuf::
|
||||
write_chars(const char *start, size_t length) {
|
||||
if (length == 0) {
|
||||
// Trivial no-op.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure the read buffer is flushed.
|
||||
size_t n = egptr() - gptr();
|
||||
gbump(n);
|
||||
|
||||
if (length == 0 || !_is_open_write) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows case.
|
||||
DWORD bytes_written = 0;
|
||||
BOOL success = WriteFile(_handle, start, length, &bytes_written, nullptr);
|
||||
if (!success) {
|
||||
assert(bytes_written <= length);
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_NO_DATA && error != ERROR_BROKEN_PIPE) {
|
||||
cerr << "Error writing " << length
|
||||
<< " bytes, windows error code 0x" << hex
|
||||
<< error << dec << ".\n";
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
assert(bytes_written == length);
|
||||
|
||||
#else
|
||||
// Posix case.
|
||||
size_t remaining = length;
|
||||
while (remaining > 0) {
|
||||
ssize_t result = ::write(_handle, start, remaining);
|
||||
if (result < 0) {
|
||||
if (errno != EPIPE) {
|
||||
cerr << "Error writing " << remaining << " bytes\n";
|
||||
}
|
||||
return length - remaining;
|
||||
}
|
||||
|
||||
start += result;
|
||||
remaining -= result;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
return length;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file handleStreamBuf.h
|
||||
* @author drose
|
||||
* @date 2009-06-05
|
||||
*/
|
||||
|
||||
#ifndef HANDLESTREAMBUF_H
|
||||
#define HANDLESTREAMBUF_H
|
||||
|
||||
#include "fhandle.h"
|
||||
#include "p3d_lock.h"
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class HandleStreamBuf : public std::streambuf {
|
||||
public:
|
||||
HandleStreamBuf();
|
||||
virtual ~HandleStreamBuf();
|
||||
|
||||
void open_read(FHandle handle);
|
||||
void open_write(FHandle handle);
|
||||
bool is_open_read() const;
|
||||
bool is_open_write() const;
|
||||
void close();
|
||||
void close_handle();
|
||||
|
||||
inline FHandle get_handle() const;
|
||||
inline bool has_gdata() const;
|
||||
|
||||
protected:
|
||||
virtual int overflow(int c);
|
||||
virtual int sync();
|
||||
virtual int underflow();
|
||||
|
||||
private:
|
||||
size_t read_chars(char *start, size_t length);
|
||||
size_t write_chars(const char *start, size_t length);
|
||||
|
||||
private:
|
||||
bool _is_open_read;
|
||||
bool _is_open_write;
|
||||
|
||||
FHandle _handle;
|
||||
LOCK _lock;
|
||||
|
||||
char *_buffer;
|
||||
};
|
||||
|
||||
#include "handleStreamBuf.I"
|
||||
|
||||
#endif
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file is_pathsep.I
|
||||
* @author drose
|
||||
* @date 2009-07-07
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns true if the indicated character is a path separator character (e.g.
|
||||
* slash or backslash), false otherwise.
|
||||
*/
|
||||
inline bool
|
||||
is_pathsep(int ch) {
|
||||
if (ch == '/') {
|
||||
return true;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (ch == '\\') {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file is_pathsep.h
|
||||
* @author drose
|
||||
* @date 2009-07-07
|
||||
*/
|
||||
|
||||
#ifndef IS_PATHSEP_H
|
||||
#define IS_PATHSEP_H
|
||||
|
||||
inline bool
|
||||
is_pathsep(int ch);
|
||||
|
||||
#include "is_pathsep.I"
|
||||
|
||||
#endif
|
||||
@@ -1,476 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file load_plugin.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-19
|
||||
*/
|
||||
|
||||
#include "load_plugin.h"
|
||||
#include "p3d_plugin_config.h"
|
||||
#include "is_pathsep.h"
|
||||
#include "wstring_encode.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
|
||||
#ifdef _WIN32
|
||||
static const string dll_ext = ".dll";
|
||||
#elif defined(__APPLE__)
|
||||
static const string dll_ext = ".dylib";
|
||||
#else
|
||||
static const string dll_ext = ".so";
|
||||
#endif
|
||||
|
||||
static const string default_plugin_filename = "p3d_plugin";
|
||||
|
||||
P3D_initialize_func *P3D_initialize_ptr;
|
||||
P3D_finalize_func *P3D_finalize_ptr;
|
||||
P3D_set_plugin_version_func *P3D_set_plugin_version_ptr;
|
||||
P3D_set_super_mirror_func *P3D_set_super_mirror_ptr;
|
||||
P3D_new_instance_func *P3D_new_instance_ptr;
|
||||
P3D_instance_start_func *P3D_instance_start_ptr;
|
||||
P3D_instance_start_stream_func *P3D_instance_start_stream_ptr;
|
||||
P3D_instance_finish_func *P3D_instance_finish_ptr;
|
||||
P3D_instance_setup_window_func *P3D_instance_setup_window_ptr;
|
||||
|
||||
P3D_object_get_type_func *P3D_object_get_type_ptr;
|
||||
P3D_object_get_bool_func *P3D_object_get_bool_ptr;
|
||||
P3D_object_get_int_func *P3D_object_get_int_ptr;
|
||||
P3D_object_get_float_func *P3D_object_get_float_ptr;
|
||||
P3D_object_get_string_func *P3D_object_get_string_ptr;
|
||||
P3D_object_get_repr_func *P3D_object_get_repr_ptr;
|
||||
P3D_object_get_property_func *P3D_object_get_property_ptr;
|
||||
P3D_object_set_property_func *P3D_object_set_property_ptr;
|
||||
P3D_object_has_method_func *P3D_object_has_method_ptr;
|
||||
P3D_object_call_func *P3D_object_call_ptr;
|
||||
P3D_object_eval_func *P3D_object_eval_ptr;
|
||||
P3D_object_incref_func *P3D_object_incref_ptr;
|
||||
P3D_object_decref_func *P3D_object_decref_ptr;
|
||||
|
||||
P3D_make_class_definition_func *P3D_make_class_definition_ptr;
|
||||
P3D_new_undefined_object_func *P3D_new_undefined_object_ptr;
|
||||
P3D_new_none_object_func *P3D_new_none_object_ptr;
|
||||
P3D_new_bool_object_func *P3D_new_bool_object_ptr;
|
||||
P3D_new_int_object_func *P3D_new_int_object_ptr;
|
||||
P3D_new_float_object_func *P3D_new_float_object_ptr;
|
||||
P3D_new_string_object_func *P3D_new_string_object_ptr;
|
||||
P3D_instance_get_panda_script_object_func *P3D_instance_get_panda_script_object_ptr;
|
||||
P3D_instance_set_browser_script_object_func *P3D_instance_set_browser_script_object_ptr;
|
||||
|
||||
P3D_instance_get_request_func *P3D_instance_get_request_ptr;
|
||||
P3D_check_request_func *P3D_check_request_ptr;
|
||||
P3D_request_finish_func *P3D_request_finish_ptr;
|
||||
P3D_instance_feed_url_stream_func *P3D_instance_feed_url_stream_ptr;
|
||||
P3D_instance_handle_event_func *P3D_instance_handle_event_ptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
static HMODULE module = nullptr;
|
||||
#else
|
||||
static void *module = nullptr;
|
||||
#endif
|
||||
|
||||
static bool plugin_loaded = false;
|
||||
static bool dso_needs_unload = false;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default plugin filename, without any directory path (but
|
||||
* including the extension appropriate to this platform).
|
||||
*/
|
||||
string
|
||||
get_plugin_basename() {
|
||||
return default_plugin_filename + dll_ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position in the string of the dot before the filename
|
||||
* extension; that is, the position of the rightmost dot that is right of the
|
||||
* rightmost slash (or backslash, on Windows). Returns string::npos if there
|
||||
* is no extension.
|
||||
*/
|
||||
static size_t
|
||||
find_extension_dot(const string &filename) {
|
||||
size_t p = filename.length();
|
||||
while (p > 0 && !is_pathsep(filename[p - 1])) {
|
||||
--p;
|
||||
if (filename[p] == '.') {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return string::npos;
|
||||
}
|
||||
|
||||
// Forward reference for function defined below.
|
||||
static void unload_dso();
|
||||
|
||||
/**
|
||||
* Loads the plugin and assigns all of the function pointers. Returns true on
|
||||
* success, false on failure. If load_plugin() has already been called
|
||||
* successfully, this returns true immediately, without parsing any
|
||||
* parameters.
|
||||
*
|
||||
* If p3d_plugin_filename is empty, the module is assumed to be already loaded
|
||||
* (or statically linked in), and the symbols are located within the current
|
||||
* address space.
|
||||
*/
|
||||
bool
|
||||
load_plugin(const string &p3d_plugin_filename,
|
||||
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, const string &host_dir,
|
||||
const string &start_dir, std::ostream &logfile) {
|
||||
if (plugin_loaded) {
|
||||
return true;
|
||||
}
|
||||
string filename = p3d_plugin_filename;
|
||||
|
||||
#ifdef _WIN32
|
||||
assert(module == nullptr);
|
||||
|
||||
if (filename.empty()) {
|
||||
// If no filename is supplied, look within our existing address space.
|
||||
module = GetModuleHandle(nullptr);
|
||||
dso_needs_unload = false;
|
||||
|
||||
} else {
|
||||
// If a filename is supplied, attempt to load it as a dynamic library.
|
||||
|
||||
// On Windows, the filename passed to LoadLibrary() must have an
|
||||
// extension, or a default ".DLL" will be implicitly added. If the file
|
||||
// actually has no extension, we must add "." to avoid this.
|
||||
|
||||
// Check whether the filename has an extension.
|
||||
size_t extension_dot = find_extension_dot(filename);
|
||||
if (extension_dot == string::npos) {
|
||||
// No extension.
|
||||
filename += ".";
|
||||
}
|
||||
|
||||
SetErrorMode(0);
|
||||
std::wstring filename_w;
|
||||
if (string_to_wstring(filename_w, filename)) {
|
||||
module = LoadLibraryW(filename_w.c_str());
|
||||
}
|
||||
dso_needs_unload = true;
|
||||
}
|
||||
|
||||
if (module == nullptr) {
|
||||
// Couldn't load the DLL.
|
||||
logfile
|
||||
<< "Couldn't load " << filename << ", error = "
|
||||
<< GetLastError() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
#define get_func GetProcAddress
|
||||
|
||||
#else // _WIN32
|
||||
// Posix case.
|
||||
assert(module == nullptr);
|
||||
if (filename.empty()) {
|
||||
module = dlopen(nullptr, RTLD_LAZY | RTLD_LOCAL);
|
||||
} else {
|
||||
module = dlopen(filename.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
}
|
||||
if (module == nullptr) {
|
||||
// Couldn't load the .so.
|
||||
const char *message = dlerror();
|
||||
if (message == nullptr) {
|
||||
message = "No error";
|
||||
}
|
||||
logfile << "Couldn't load " << filename << ": " << message << "\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
dso_needs_unload = true;
|
||||
|
||||
#define get_func dlsym
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
// Now get all of the function pointers.
|
||||
P3D_initialize_ptr = (P3D_initialize_func *)get_func(module, "P3D_initialize");
|
||||
P3D_finalize_ptr = (P3D_finalize_func *)get_func(module, "P3D_finalize");
|
||||
P3D_set_plugin_version_ptr = (P3D_set_plugin_version_func *)get_func(module, "P3D_set_plugin_version");
|
||||
P3D_set_super_mirror_ptr = (P3D_set_super_mirror_func *)get_func(module, "P3D_set_super_mirror");
|
||||
P3D_new_instance_ptr = (P3D_new_instance_func *)get_func(module, "P3D_new_instance");
|
||||
P3D_instance_start_ptr = (P3D_instance_start_func *)get_func(module, "P3D_instance_start");
|
||||
P3D_instance_start_stream_ptr = (P3D_instance_start_stream_func *)get_func(module, "P3D_instance_start_stream");
|
||||
P3D_instance_finish_ptr = (P3D_instance_finish_func *)get_func(module, "P3D_instance_finish");
|
||||
P3D_instance_setup_window_ptr = (P3D_instance_setup_window_func *)get_func(module, "P3D_instance_setup_window");
|
||||
|
||||
P3D_object_get_type_ptr = (P3D_object_get_type_func *)get_func(module, "P3D_object_get_type");
|
||||
P3D_object_get_bool_ptr = (P3D_object_get_bool_func *)get_func(module, "P3D_object_get_bool");
|
||||
P3D_object_get_int_ptr = (P3D_object_get_int_func *)get_func(module, "P3D_object_get_int");
|
||||
P3D_object_get_float_ptr = (P3D_object_get_float_func *)get_func(module, "P3D_object_get_float");
|
||||
P3D_object_get_string_ptr = (P3D_object_get_string_func *)get_func(module, "P3D_object_get_string");
|
||||
P3D_object_get_repr_ptr = (P3D_object_get_repr_func *)get_func(module, "P3D_object_get_repr");
|
||||
P3D_object_get_property_ptr = (P3D_object_get_property_func *)get_func(module, "P3D_object_get_property");
|
||||
P3D_object_set_property_ptr = (P3D_object_set_property_func *)get_func(module, "P3D_object_set_property");
|
||||
P3D_object_has_method_ptr = (P3D_object_has_method_func *)get_func(module, "P3D_object_has_method");
|
||||
P3D_object_call_ptr = (P3D_object_call_func *)get_func(module, "P3D_object_call");
|
||||
P3D_object_eval_ptr = (P3D_object_eval_func *)get_func(module, "P3D_object_eval");
|
||||
P3D_object_incref_ptr = (P3D_object_incref_func *)get_func(module, "P3D_object_incref");
|
||||
P3D_object_decref_ptr = (P3D_object_decref_func *)get_func(module, "P3D_object_decref");
|
||||
P3D_make_class_definition_ptr = (P3D_make_class_definition_func *)get_func(module, "P3D_make_class_definition");
|
||||
P3D_new_undefined_object_ptr = (P3D_new_undefined_object_func *)get_func(module, "P3D_new_undefined_object");
|
||||
P3D_new_none_object_ptr = (P3D_new_none_object_func *)get_func(module, "P3D_new_none_object");
|
||||
P3D_new_bool_object_ptr = (P3D_new_bool_object_func *)get_func(module, "P3D_new_bool_object");
|
||||
P3D_new_int_object_ptr = (P3D_new_int_object_func *)get_func(module, "P3D_new_int_object");
|
||||
P3D_new_float_object_ptr = (P3D_new_float_object_func *)get_func(module, "P3D_new_float_object");
|
||||
P3D_new_string_object_ptr = (P3D_new_string_object_func *)get_func(module, "P3D_new_string_object");
|
||||
P3D_instance_get_panda_script_object_ptr = (P3D_instance_get_panda_script_object_func *)get_func(module, "P3D_instance_get_panda_script_object");
|
||||
P3D_instance_set_browser_script_object_ptr = (P3D_instance_set_browser_script_object_func *)get_func(module, "P3D_instance_set_browser_script_object");
|
||||
|
||||
P3D_instance_get_request_ptr = (P3D_instance_get_request_func *)get_func(module, "P3D_instance_get_request");
|
||||
P3D_check_request_ptr = (P3D_check_request_func *)get_func(module, "P3D_check_request");
|
||||
P3D_request_finish_ptr = (P3D_request_finish_func *)get_func(module, "P3D_request_finish");
|
||||
P3D_instance_feed_url_stream_ptr = (P3D_instance_feed_url_stream_func *)get_func(module, "P3D_instance_feed_url_stream");
|
||||
P3D_instance_handle_event_ptr = (P3D_instance_handle_event_func *)get_func(module, "P3D_instance_handle_event");
|
||||
|
||||
#undef get_func
|
||||
|
||||
// Successfully loaded.
|
||||
plugin_loaded = true;
|
||||
|
||||
if (!init_plugin(contents_filename, host_url,
|
||||
verify_contents, platform,
|
||||
log_directory, log_basename,
|
||||
trusted_environment, console_environment,
|
||||
root_dir, host_dir, start_dir, logfile)) {
|
||||
unload_dso();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures all the required function pointers have been set, and then calls
|
||||
* P3D_initialize() on the recently-loaded plugin. Returns true on success,
|
||||
* false on failure.
|
||||
*
|
||||
* It is not necessary to call this after calling load_plugin(); it is called
|
||||
* implicitly.
|
||||
*/
|
||||
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, const string &host_dir,
|
||||
const string &start_dir, std::ostream &logfile) {
|
||||
|
||||
// Ensure that all of the function pointers have been found.
|
||||
if (P3D_initialize_ptr == nullptr ||
|
||||
P3D_finalize_ptr == nullptr ||
|
||||
P3D_set_plugin_version_ptr == nullptr ||
|
||||
P3D_set_super_mirror_ptr == nullptr ||
|
||||
P3D_new_instance_ptr == nullptr ||
|
||||
P3D_instance_start_ptr == nullptr ||
|
||||
P3D_instance_start_stream_ptr == nullptr ||
|
||||
P3D_instance_finish_ptr == nullptr ||
|
||||
P3D_instance_setup_window_ptr == nullptr ||
|
||||
|
||||
P3D_object_get_type_ptr == nullptr ||
|
||||
P3D_object_get_bool_ptr == nullptr ||
|
||||
P3D_object_get_int_ptr == nullptr ||
|
||||
P3D_object_get_float_ptr == nullptr ||
|
||||
P3D_object_get_string_ptr == nullptr ||
|
||||
P3D_object_get_repr_ptr == nullptr ||
|
||||
P3D_object_get_property_ptr == nullptr ||
|
||||
P3D_object_set_property_ptr == nullptr ||
|
||||
P3D_object_has_method_ptr == nullptr ||
|
||||
P3D_object_call_ptr == nullptr ||
|
||||
P3D_object_eval_ptr == nullptr ||
|
||||
P3D_object_incref_ptr == nullptr ||
|
||||
P3D_object_decref_ptr == nullptr ||
|
||||
|
||||
P3D_make_class_definition_ptr == nullptr ||
|
||||
P3D_new_undefined_object_ptr == nullptr ||
|
||||
P3D_new_none_object_ptr == nullptr ||
|
||||
P3D_new_bool_object_ptr == nullptr ||
|
||||
P3D_new_int_object_ptr == nullptr ||
|
||||
P3D_new_float_object_ptr == nullptr ||
|
||||
P3D_new_string_object_ptr == nullptr ||
|
||||
P3D_instance_get_panda_script_object_ptr == nullptr ||
|
||||
P3D_instance_set_browser_script_object_ptr == nullptr ||
|
||||
|
||||
P3D_instance_get_request_ptr == nullptr ||
|
||||
P3D_check_request_ptr == nullptr ||
|
||||
P3D_request_finish_ptr == nullptr ||
|
||||
P3D_instance_feed_url_stream_ptr == nullptr ||
|
||||
P3D_instance_handle_event_ptr == nullptr) {
|
||||
|
||||
logfile
|
||||
<< "Some function pointers not found:"
|
||||
<< "\nP3D_initialize_ptr = " << P3D_initialize_ptr
|
||||
<< "\nP3D_finalize_ptr = " << P3D_finalize_ptr
|
||||
<< "\nP3D_set_plugin_version_ptr = " << P3D_set_plugin_version_ptr
|
||||
<< "\nP3D_set_super_mirror_ptr = " << P3D_set_super_mirror_ptr
|
||||
<< "\nP3D_new_instance_ptr = " << P3D_new_instance_ptr
|
||||
<< "\nP3D_instance_start_ptr = " << P3D_instance_start_ptr
|
||||
<< "\nP3D_instance_start_stream_ptr = " << P3D_instance_start_stream_ptr
|
||||
<< "\nP3D_instance_finish_ptr = " << P3D_instance_finish_ptr
|
||||
<< "\nP3D_instance_setup_window_ptr = " << P3D_instance_setup_window_ptr
|
||||
|
||||
<< "\nP3D_object_get_type_ptr = " << P3D_object_get_type_ptr
|
||||
<< "\nP3D_object_get_bool_ptr = " << P3D_object_get_bool_ptr
|
||||
<< "\nP3D_object_get_int_ptr = " << P3D_object_get_int_ptr
|
||||
<< "\nP3D_object_get_float_ptr = " << P3D_object_get_float_ptr
|
||||
<< "\nP3D_object_get_string_ptr = " << P3D_object_get_string_ptr
|
||||
<< "\nP3D_object_get_repr_ptr = " << P3D_object_get_repr_ptr
|
||||
<< "\nP3D_object_get_property_ptr = " << P3D_object_get_property_ptr
|
||||
<< "\nP3D_object_set_property_ptr = " << P3D_object_set_property_ptr
|
||||
<< "\nP3D_object_has_method_ptr = " << P3D_object_has_method_ptr
|
||||
<< "\nP3D_object_call_ptr = " << P3D_object_call_ptr
|
||||
<< "\nP3D_object_eval_ptr = " << P3D_object_eval_ptr
|
||||
<< "\nP3D_object_incref_ptr = " << P3D_object_incref_ptr
|
||||
<< "\nP3D_object_decref_ptr = " << P3D_object_decref_ptr
|
||||
|
||||
<< "\nP3D_make_class_definition_ptr = " << P3D_make_class_definition_ptr
|
||||
<< "\nP3D_new_undefined_object_ptr = " << P3D_new_undefined_object_ptr
|
||||
<< "\nP3D_new_none_object_ptr = " << P3D_new_none_object_ptr
|
||||
<< "\nP3D_new_bool_object_ptr = " << P3D_new_bool_object_ptr
|
||||
<< "\nP3D_new_int_object_ptr = " << P3D_new_int_object_ptr
|
||||
<< "\nP3D_new_float_object_ptr = " << P3D_new_float_object_ptr
|
||||
<< "\nP3D_new_string_object_ptr = " << P3D_new_string_object_ptr
|
||||
<< "\nP3D_instance_get_panda_script_object_ptr = " << P3D_instance_get_panda_script_object_ptr
|
||||
<< "\nP3D_instance_set_browser_script_object_ptr = " << P3D_instance_set_browser_script_object_ptr
|
||||
|
||||
<< "\nP3D_instance_get_request_ptr = " << P3D_instance_get_request_ptr
|
||||
<< "\nP3D_check_request_ptr = " << P3D_check_request_ptr
|
||||
<< "\nP3D_request_finish_ptr = " << P3D_request_finish_ptr
|
||||
<< "\nP3D_instance_feed_url_stream_ptr = " << P3D_instance_feed_url_stream_ptr
|
||||
<< "\nP3D_instance_handle_event_ptr = " << P3D_instance_handle_event_ptr
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A bit of extra hand-hacked compatibility for using newer plug-ins with an
|
||||
// older version of the core API.
|
||||
int api_version = P3D_API_VERSION;
|
||||
if (api_version == 17 && start_dir.empty()) {
|
||||
api_version = 16;
|
||||
if (host_dir.empty()) {
|
||||
api_version = 15;
|
||||
}
|
||||
}
|
||||
|
||||
if (!P3D_initialize_ptr(api_version, contents_filename.c_str(),
|
||||
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(), host_dir.c_str(),
|
||||
start_dir.c_str())) {
|
||||
// Oops, failure to initialize.
|
||||
logfile
|
||||
<< "Failed to initialize plugin (passed API version "
|
||||
<< api_version << ")\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls finalize, then removes the plugin from memory space and clears all of
|
||||
* the pointers.
|
||||
*/
|
||||
void
|
||||
unload_plugin(std::ostream &logfile) {
|
||||
if (!plugin_loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
P3D_finalize_ptr();
|
||||
unload_dso();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the plugin from memory space and clears all of the pointers. This
|
||||
* is only intended to be called by load_plugin(), above, in the specific case
|
||||
* that the plugin loaded but could not successfully initialize itself. All
|
||||
* user code should call unload_plugin(), above, which first calls
|
||||
* P3D_finalize().
|
||||
*/
|
||||
static void
|
||||
unload_dso() {
|
||||
if (dso_needs_unload) {
|
||||
assert(module != nullptr);
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(module);
|
||||
#else
|
||||
dlclose(module);
|
||||
#endif
|
||||
module = nullptr;
|
||||
dso_needs_unload = false;
|
||||
}
|
||||
|
||||
P3D_initialize_ptr = nullptr;
|
||||
P3D_finalize_ptr = nullptr;
|
||||
P3D_set_plugin_version_ptr = nullptr;
|
||||
P3D_set_super_mirror_ptr = nullptr;
|
||||
P3D_new_instance_ptr = nullptr;
|
||||
P3D_instance_start_ptr = nullptr;
|
||||
P3D_instance_start_stream_ptr = nullptr;
|
||||
P3D_instance_finish_ptr = nullptr;
|
||||
P3D_instance_setup_window_ptr = nullptr;
|
||||
|
||||
P3D_object_get_type_ptr = nullptr;
|
||||
P3D_object_get_bool_ptr = nullptr;
|
||||
P3D_object_get_int_ptr = nullptr;
|
||||
P3D_object_get_float_ptr = nullptr;
|
||||
P3D_object_get_string_ptr = nullptr;
|
||||
P3D_object_get_repr_ptr = nullptr;
|
||||
P3D_object_get_property_ptr = nullptr;
|
||||
P3D_object_set_property_ptr = nullptr;
|
||||
P3D_object_has_method_ptr = nullptr;
|
||||
P3D_object_call_ptr = nullptr;
|
||||
P3D_object_eval_ptr = nullptr;
|
||||
P3D_object_incref_ptr = nullptr;
|
||||
P3D_object_decref_ptr = nullptr;
|
||||
|
||||
P3D_make_class_definition_ptr = nullptr;
|
||||
P3D_new_undefined_object_ptr = nullptr;
|
||||
P3D_new_none_object_ptr = nullptr;
|
||||
P3D_new_bool_object_ptr = nullptr;
|
||||
P3D_new_int_object_ptr = nullptr;
|
||||
P3D_new_float_object_ptr = nullptr;
|
||||
P3D_new_string_object_ptr = nullptr;
|
||||
P3D_instance_get_panda_script_object_ptr = nullptr;
|
||||
P3D_instance_set_browser_script_object_ptr = nullptr;
|
||||
|
||||
P3D_instance_get_request_ptr = nullptr;
|
||||
P3D_check_request_ptr = nullptr;
|
||||
P3D_request_finish_ptr = nullptr;
|
||||
P3D_instance_feed_url_stream_ptr = nullptr;
|
||||
P3D_instance_handle_event_ptr = nullptr;
|
||||
|
||||
plugin_loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the plugin has been loaded successfully by a previous call
|
||||
* to load_plugin(), false otherwise.
|
||||
*/
|
||||
bool
|
||||
is_plugin_loaded() {
|
||||
return plugin_loaded;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file load_plugin.h
|
||||
* @author drose
|
||||
* @date 2009-06-19
|
||||
*/
|
||||
|
||||
#ifndef LOAD_PLUGIN_H
|
||||
#define LOAD_PLUGIN_H
|
||||
|
||||
#include "p3d_plugin.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
extern P3D_initialize_func *P3D_initialize_ptr;
|
||||
extern P3D_finalize_func *P3D_finalize_ptr;
|
||||
extern P3D_set_plugin_version_func *P3D_set_plugin_version_ptr;
|
||||
extern P3D_set_super_mirror_func *P3D_set_super_mirror_ptr;
|
||||
extern P3D_new_instance_func *P3D_new_instance_ptr;
|
||||
extern P3D_instance_start_func *P3D_instance_start_ptr;
|
||||
extern P3D_instance_start_stream_func *P3D_instance_start_stream_ptr;
|
||||
extern P3D_instance_finish_func *P3D_instance_finish_ptr;
|
||||
extern P3D_instance_setup_window_func *P3D_instance_setup_window_ptr;
|
||||
|
||||
extern P3D_object_get_type_func *P3D_object_get_type_ptr;
|
||||
extern P3D_object_get_bool_func *P3D_object_get_bool_ptr;
|
||||
extern P3D_object_get_int_func *P3D_object_get_int_ptr;
|
||||
extern P3D_object_get_float_func *P3D_object_get_float_ptr;
|
||||
extern P3D_object_get_string_func *P3D_object_get_string_ptr;
|
||||
extern P3D_object_get_repr_func *P3D_object_get_repr_ptr;
|
||||
extern P3D_object_get_property_func *P3D_object_get_property_ptr;
|
||||
extern P3D_object_set_property_func *P3D_object_set_property_ptr;
|
||||
extern P3D_object_has_method_func *P3D_object_has_method_ptr;
|
||||
extern P3D_object_call_func *P3D_object_call_ptr;
|
||||
extern P3D_object_eval_func *P3D_object_eval_ptr;
|
||||
extern P3D_object_incref_func *P3D_object_incref_ptr;
|
||||
extern P3D_object_decref_func *P3D_object_decref_ptr;
|
||||
|
||||
extern P3D_make_class_definition_func *P3D_make_class_definition_ptr;
|
||||
extern P3D_new_undefined_object_func *P3D_new_undefined_object_ptr;
|
||||
extern P3D_new_none_object_func *P3D_new_none_object_ptr;
|
||||
extern P3D_new_bool_object_func *P3D_new_bool_object_ptr;
|
||||
extern P3D_new_int_object_func *P3D_new_int_object_ptr;
|
||||
extern P3D_new_float_object_func *P3D_new_float_object_ptr;
|
||||
extern P3D_new_string_object_func *P3D_new_string_object_ptr;
|
||||
extern P3D_instance_get_panda_script_object_func *P3D_instance_get_panda_script_object_ptr;
|
||||
extern P3D_instance_set_browser_script_object_func *P3D_instance_set_browser_script_object_ptr;
|
||||
|
||||
extern P3D_instance_get_request_func *P3D_instance_get_request_ptr;
|
||||
extern P3D_check_request_func *P3D_check_request_ptr;
|
||||
extern P3D_request_finish_func *P3D_request_finish_ptr;
|
||||
extern P3D_instance_feed_url_stream_func *P3D_instance_feed_url_stream_ptr;
|
||||
extern P3D_instance_handle_event_func *P3D_instance_handle_event_ptr;
|
||||
|
||||
std::string get_plugin_basename();
|
||||
bool
|
||||
load_plugin(const std::string &p3d_plugin_filename,
|
||||
const std::string &contents_filename, const std::string &host_url,
|
||||
P3D_verify_contents verify_contents, const std::string &platform,
|
||||
const std::string &log_directory, const std::string &log_basename,
|
||||
bool trusted_environment, bool console_environment,
|
||||
const std::string &root_dir, const std::string &host_dir,
|
||||
const std::string &start_dir, std::ostream &logfile);
|
||||
bool
|
||||
init_plugin(const std::string &contents_filename, const std::string &host_url,
|
||||
P3D_verify_contents verify_contents, const std::string &platform,
|
||||
const std::string &log_directory, const std::string &log_basename,
|
||||
bool trusted_environment, bool console_environment,
|
||||
const std::string &root_dir, const std::string &host_dir,
|
||||
const std::string &start_dir, std::ostream &logfile);
|
||||
|
||||
void unload_plugin(std::ostream &logfile);
|
||||
bool is_plugin_loaded();
|
||||
|
||||
#endif
|
||||
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file mkdir_complete.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
#include "mkdir_complete.h"
|
||||
#include "is_pathsep.h"
|
||||
#include "wstring_encode.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h> // chmod()
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h> // for mkdir()
|
||||
#include <errno.h>
|
||||
#include <string.h> // strerror()
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using std::ostream;
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
|
||||
/**
|
||||
* Returns the directory component of the indicated pathname, or the empty
|
||||
* string if there is no directory prefix.
|
||||
*/
|
||||
static string
|
||||
get_dirname(const string &filename) {
|
||||
size_t p = filename.length();
|
||||
while (p > 0) {
|
||||
--p;
|
||||
if (is_pathsep(filename[p])) {
|
||||
return filename.substr(0, p);
|
||||
}
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* The wide-character implementation of get_dirname(). Only implemented (and
|
||||
* needed) on Windows.
|
||||
*/
|
||||
static wstring
|
||||
get_dirname_w(const wstring &filename) {
|
||||
size_t p = filename.length();
|
||||
while (p > 0) {
|
||||
--p;
|
||||
if (is_pathsep(filename[p])) {
|
||||
return filename.substr(0, p);
|
||||
}
|
||||
}
|
||||
|
||||
return wstring();
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new directory, with normal access privileges. Returns true on
|
||||
* success, false on failure. Will create intervening directories if
|
||||
* necessary.
|
||||
*/
|
||||
bool
|
||||
mkdir_complete(const string &dirname, ostream &logfile) {
|
||||
#ifdef _WIN32
|
||||
wstring dirname_w;
|
||||
if (!string_to_wstring(dirname_w, dirname)) {
|
||||
return false;
|
||||
}
|
||||
return mkdir_complete_w(dirname_w, logfile);
|
||||
|
||||
#else //_WIN32
|
||||
if (mkdir(dirname.c_str(), 0755) == 0) {
|
||||
// Success!
|
||||
return true;
|
||||
}
|
||||
|
||||
// Failed.
|
||||
if (errno == EEXIST) {
|
||||
// Not really an error: the directory is already there.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (errno == ENOENT || errno == EACCES) {
|
||||
// We need to make the parent directory first.
|
||||
string parent = get_dirname(dirname);
|
||||
if (!parent.empty() && mkdir_complete(parent, logfile)) {
|
||||
// Parent successfully created. Try again to make the child.
|
||||
if (mkdir(dirname.c_str(), 0755) == 0) {
|
||||
// Got it!
|
||||
return true;
|
||||
}
|
||||
// Couldn't create the directory. :(
|
||||
logfile
|
||||
<< "Couldn't create " << dirname << ": " << strerror(errno) << "\n";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file with normal access priviledges. Returns true on
|
||||
* success, false on failure. This will create intervening directories if
|
||||
* needed.
|
||||
*/
|
||||
bool
|
||||
mkfile_complete(const string &filename, ostream &logfile) {
|
||||
#ifdef _WIN32
|
||||
wstring filename_w;
|
||||
if (!string_to_wstring(filename_w, filename)) {
|
||||
return false;
|
||||
}
|
||||
return mkfile_complete_w(filename_w, logfile);
|
||||
#else // _WIN32
|
||||
// Make sure we delete any previously-existing file first.
|
||||
unlink(filename.c_str());
|
||||
|
||||
int fd = creat(filename.c_str(), 0755);
|
||||
if (fd == -1) {
|
||||
// Try to make the parent directory first.
|
||||
string parent = get_dirname(filename);
|
||||
if (!parent.empty() && mkdir_complete(parent, logfile)) {
|
||||
// Parent successfully created. Try again to make the file.
|
||||
fd = creat(filename.c_str(), 0755);
|
||||
}
|
||||
if (fd == -1) {
|
||||
logfile
|
||||
<< "Couldn't create " << filename << ": " << strerror(errno) << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return true;
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* The wide-character implementation of mkdir_complete(). Only implemented
|
||||
* (and needed) on Windows.
|
||||
*/
|
||||
bool
|
||||
mkdir_complete_w(const wstring &dirname, ostream &logfile) {
|
||||
if (CreateDirectoryW(dirname.c_str(), nullptr) != 0) {
|
||||
// Success!
|
||||
return true;
|
||||
}
|
||||
|
||||
// Failed.
|
||||
DWORD last_error = GetLastError();
|
||||
if (last_error == ERROR_ALREADY_EXISTS) {
|
||||
// Not really an error: the directory is already there.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (last_error == ERROR_PATH_NOT_FOUND) {
|
||||
// We need to make the parent directory first.
|
||||
wstring parent = get_dirname_w(dirname);
|
||||
if (!parent.empty() && mkdir_complete_w(parent, logfile)) {
|
||||
// Parent successfully created. Try again to make the child.
|
||||
if (CreateDirectoryW(dirname.c_str(), nullptr) != 0) {
|
||||
// Got it!
|
||||
return true;
|
||||
}
|
||||
logfile
|
||||
<< "Couldn't create " << dirname << "\n";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* The wide-character implementation of mkfile_complete(). Only implemented
|
||||
* (and needed) on Windows.
|
||||
*/
|
||||
bool
|
||||
mkfile_complete_w(const wstring &filename, ostream &logfile) {
|
||||
// Make sure we delete any previously-existing file first.
|
||||
|
||||
// Windows can't delete a file if it's read-only. Weird.
|
||||
_wchmod(filename.c_str(), 0644);
|
||||
_wunlink(filename.c_str());
|
||||
|
||||
HANDLE file = CreateFileW(filename.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
// Try to make the parent directory first.
|
||||
wstring parent = get_dirname_w(filename);
|
||||
if (!parent.empty() && mkdir_complete_w(parent, logfile)) {
|
||||
// Parent successfully created. Try again to make the file.
|
||||
file = CreateFileW(filename.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
}
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
logfile
|
||||
<< "Couldn't create " << filename << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CloseHandle(file);
|
||||
return true;
|
||||
}
|
||||
#endif // _WIN32
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file mkdir_complete.h
|
||||
* @author drose
|
||||
* @date 2009-06-29
|
||||
*/
|
||||
|
||||
#ifndef MKDIR_COMPLETE_H
|
||||
#define MKDIR_COMPLETE_H
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
bool mkdir_complete(const std::string &dirname, std::ostream &logfile);
|
||||
bool mkfile_complete(const std::string &dirname, std::ostream &logfile);
|
||||
|
||||
#ifdef _WIN32
|
||||
bool mkdir_complete_w(const std::wstring &dirname, std::ostream &logfile);
|
||||
bool mkfile_complete_w(const std::wstring &dirname, std::ostream &logfile);
|
||||
#endif // _WIN32
|
||||
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dAuthSession.I
|
||||
* @author drose
|
||||
* @date 2009-09-17
|
||||
*/
|
||||
@@ -1,456 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dAuthSession.cxx
|
||||
* @author drose
|
||||
* @date 2009-09-17
|
||||
*/
|
||||
|
||||
#include "p3dAuthSession.h"
|
||||
#include "p3dInstance.h"
|
||||
#include "p3dInstanceManager.h"
|
||||
#include "p3dMultifileReader.h"
|
||||
#include "p3d_plugin_config.h"
|
||||
#include "mkdir_complete.h"
|
||||
#include "wstring_encode.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/select.h>
|
||||
#include <signal.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DAuthSession::
|
||||
P3DAuthSession(P3DInstance *inst) :
|
||||
_inst(inst)
|
||||
{
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
#ifdef _WIN32
|
||||
_p3dcert_handle = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
_p3dcert_pid = -1;
|
||||
#endif
|
||||
_p3dcert_started = false;
|
||||
_p3dcert_running = false;
|
||||
_started_wait_thread = false;
|
||||
INIT_THREAD(_wait_thread);
|
||||
|
||||
// Write the cert chain to a temporary file.
|
||||
_cert_filename = new P3DTemporaryFile(".crt");
|
||||
string filename = _cert_filename->get_filename();
|
||||
FILE *fp = fopen(filename.c_str(), "w");
|
||||
if (fp == nullptr) {
|
||||
nout << "Couldn't write temporary file\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst->_mf_reader.get_num_signatures() > 0) {
|
||||
const P3DMultifileReader::CertChain &cert_chain =
|
||||
inst->_mf_reader.get_signature(0);
|
||||
|
||||
if (cert_chain.size() > 0) {
|
||||
// Save the cert_dir, this is where the p3dcert program will need to
|
||||
// write the cert when it is approved.
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
_cert_dir = inst_mgr->get_cert_dir(cert_chain[0]._cert);
|
||||
}
|
||||
|
||||
P3DMultifileReader::CertChain::const_iterator ci;
|
||||
for (ci = cert_chain.begin(); ci != cert_chain.end(); ++ci) {
|
||||
X509 *c = (*ci)._cert;
|
||||
PEM_write_X509(fp, c);
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
start_p3dcert();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DAuthSession::
|
||||
~P3DAuthSession() {
|
||||
shutdown(false);
|
||||
|
||||
if (_cert_filename != nullptr) {
|
||||
delete _cert_filename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates the session by killing the subprocess.
|
||||
*/
|
||||
void P3DAuthSession::
|
||||
shutdown(bool send_message) {
|
||||
if (!send_message) {
|
||||
// If we're not to send the instance the shutdown message as a result of
|
||||
// this, then clear the _inst pointer now.
|
||||
_inst = nullptr;
|
||||
}
|
||||
|
||||
if (_p3dcert_running) {
|
||||
nout << "Killing p3dcert process\n";
|
||||
#ifdef _WIN32
|
||||
TerminateProcess(_p3dcert_handle, 2);
|
||||
CloseHandle(_p3dcert_handle);
|
||||
|
||||
#else // _WIN32
|
||||
kill(_p3dcert_pid, SIGKILL);
|
||||
|
||||
// Wait a few milliseconds for the process to exit, and then get its
|
||||
// return status to clean up the zombie status. If we don't wait long
|
||||
// enough, don't sweat it.
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100000;
|
||||
select(0, nullptr, nullptr, nullptr, &tv);
|
||||
int status;
|
||||
waitpid(_p3dcert_pid, &status, WNOHANG);
|
||||
#endif // _WIN32
|
||||
|
||||
_p3dcert_running = false;
|
||||
}
|
||||
_p3dcert_started = false;
|
||||
|
||||
// Now that the process has stopped, the thread should stop itself quickly
|
||||
// too.
|
||||
join_wait_thread();
|
||||
|
||||
// We're no longer bound to any particular instance.
|
||||
_inst = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the p3dcert program running in a child process.
|
||||
*/
|
||||
void P3DAuthSession::
|
||||
start_p3dcert() {
|
||||
if (_p3dcert_started) {
|
||||
// Already started.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_inst->_p3dcert_package == nullptr) {
|
||||
nout << "Couldn't start Python: no p3dcert package.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
_start_dir = inst_mgr->get_start_dir();
|
||||
|
||||
string root_dir = _inst->_p3dcert_package->get_package_dir();
|
||||
mkdir_complete(_start_dir, nout);
|
||||
|
||||
_p3dcert_exe = root_dir + "/p3dcert";
|
||||
#ifdef _WIN32
|
||||
_p3dcert_exe += ".exe";
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
// On OSX, run from the packaged bundle.
|
||||
_p3dcert_exe = root_dir + "/P3DCert.app/Contents/MacOS/p3dcert";
|
||||
#endif
|
||||
|
||||
// Populate the new process' environment.
|
||||
|
||||
#ifdef _WIN32
|
||||
// These are the enviroment variables we forward from the current
|
||||
// environment, if they are set.
|
||||
const wchar_t *keep[] = {
|
||||
L"TMP", L"TEMP", L"HOME", L"USER",
|
||||
L"SYSTEMROOT", L"USERPROFILE", L"COMSPEC",
|
||||
nullptr
|
||||
};
|
||||
|
||||
std::wstring env_w;
|
||||
|
||||
for (int ki = 0; keep[ki] != nullptr; ++ki) {
|
||||
wchar_t *value = _wgetenv(keep[ki]);
|
||||
if (value != nullptr) {
|
||||
env_w += keep[ki];
|
||||
env_w += L"=";
|
||||
env_w += value;
|
||||
env_w += (wchar_t)'\0';
|
||||
}
|
||||
}
|
||||
|
||||
wstring_to_string(_env, env_w);
|
||||
|
||||
#else // _WIN32
|
||||
|
||||
_env = string();
|
||||
|
||||
// These are the enviroment variables we forward from the current
|
||||
// environment, if they are set.
|
||||
const char *keep[] = {
|
||||
"TMP", "TEMP", "HOME", "USER",
|
||||
"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG",
|
||||
#ifdef HAVE_X11
|
||||
"DISPLAY", "XAUTHORITY",
|
||||
#endif
|
||||
nullptr
|
||||
};
|
||||
for (int ki = 0; keep[ki] != nullptr; ++ki) {
|
||||
char *value = getenv(keep[ki]);
|
||||
if (value != nullptr) {
|
||||
_env += keep[ki];
|
||||
_env += "=";
|
||||
_env += value;
|
||||
_env += '\0';
|
||||
}
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// Define some new environment variables.
|
||||
_env += "PATH=";
|
||||
_env += root_dir;
|
||||
_env += '\0';
|
||||
|
||||
_env += "LD_LIBRARY_PATH=";
|
||||
_env += root_dir;
|
||||
_env += '\0';
|
||||
|
||||
_env += "DYLD_LIBRARY_PATH=";
|
||||
_env += root_dir;
|
||||
_env += '\0';
|
||||
|
||||
_env += "P3DCERT_ROOT=";
|
||||
_env += root_dir;
|
||||
_env += '\0';
|
||||
|
||||
nout << "Setting environment:\n";
|
||||
write_env();
|
||||
|
||||
nout << "Attempting to start p3dcert from " << _p3dcert_exe << "\n";
|
||||
|
||||
bool started_p3dcert = false;
|
||||
#ifdef _WIN32
|
||||
_p3dcert_handle = win_create_process();
|
||||
started_p3dcert = (_p3dcert_handle != INVALID_HANDLE_VALUE);
|
||||
#else
|
||||
_p3dcert_pid = posix_create_process();
|
||||
started_p3dcert = (_p3dcert_pid > 0);
|
||||
#endif
|
||||
if (!started_p3dcert) {
|
||||
nout << "Failed to create process.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
_p3dcert_started = true;
|
||||
_p3dcert_running = true;
|
||||
|
||||
spawn_wait_thread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the wait thread. This thread is responsible for waiting for the
|
||||
* process to finish, and notifying the instance when it does.
|
||||
*/
|
||||
void P3DAuthSession::
|
||||
spawn_wait_thread() {
|
||||
SPAWN_THREAD(_wait_thread, wt_thread_run, this);
|
||||
_started_wait_thread = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the wait thread to stop.
|
||||
*/
|
||||
void P3DAuthSession::
|
||||
join_wait_thread() {
|
||||
if (!_started_wait_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
JOIN_THREAD(_wait_thread);
|
||||
_started_wait_thread = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes _env, which is formatted as a string containing zero-byte-terminated
|
||||
* environment defintions, to the nout stream, one definition per line.
|
||||
*/
|
||||
void P3DAuthSession::
|
||||
write_env() const {
|
||||
size_t p = 0;
|
||||
size_t zero = _env.find('\0', p);
|
||||
while (zero != string::npos) {
|
||||
nout << " ";
|
||||
nout.write(_env.data() + p, zero - p);
|
||||
nout << "\n";
|
||||
p = zero + 1;
|
||||
zero = _env.find('\0', p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function for the wait thread.
|
||||
*/
|
||||
void P3DAuthSession::
|
||||
wt_thread_run() {
|
||||
// All we do here is wait for the process to terminate.
|
||||
nout << "wt_thread_run in " << this << " beginning\n";
|
||||
#ifdef _WIN32
|
||||
DWORD result = WaitForSingleObject(_p3dcert_handle, INFINITE);
|
||||
if (result != 0) {
|
||||
nout << "Wait for process failed: " << GetLastError() << "\n";
|
||||
}
|
||||
CloseHandle(_p3dcert_handle);
|
||||
_p3dcert_handle = INVALID_HANDLE_VALUE;
|
||||
_p3dcert_running = false;
|
||||
nout << "p3dcert process has successfully stopped.\n";
|
||||
#else
|
||||
int status;
|
||||
pid_t result = waitpid(_p3dcert_pid, &status, 0);
|
||||
if (result == -1) {
|
||||
perror("waitpid");
|
||||
}
|
||||
_p3dcert_pid = -1;
|
||||
_p3dcert_running = false;
|
||||
|
||||
nout << "p3dcert process has successfully stopped.\n";
|
||||
if (WIFEXITED(status)) {
|
||||
nout << " exited normally, status = "
|
||||
<< WEXITSTATUS(status) << "\n";
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
nout << " signalled by " << WTERMSIG(status) << ", core = "
|
||||
<< WCOREDUMP(status) << "\n";
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
nout << " stopped by " << WSTOPSIG(status) << "\n";
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// Notify the instance that we're done.
|
||||
P3DInstance *inst = _inst;
|
||||
if (inst != nullptr) {
|
||||
inst->auth_finished_sub_thread();
|
||||
}
|
||||
|
||||
nout << "Exiting wt_thread_run in " << this << "\n";
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Creates a sub-process to run _p3dcert_exe, with the appropriate command-
|
||||
* line arguments, and the environment string defined in _env.
|
||||
*
|
||||
* Returns the handle to the created process on success, or
|
||||
* INVALID_HANDLE_VALUE on falure.
|
||||
*/
|
||||
HANDLE P3DAuthSession::
|
||||
win_create_process() {
|
||||
// Make sure we see an error dialog if there is a missing DLL.
|
||||
SetErrorMode(0);
|
||||
|
||||
STARTUPINFO startup_info;
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
|
||||
// Make sure the initial window is *shown* for this graphical app.
|
||||
startup_info.wShowWindow = SW_SHOW;
|
||||
startup_info.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
|
||||
const char *start_dir_cstr = _start_dir.c_str();
|
||||
|
||||
// Construct the command-line string, containing the quoted command-line
|
||||
// arguments.
|
||||
std::ostringstream stream;
|
||||
stream << "\"" << _p3dcert_exe << "\" \""
|
||||
<< _cert_filename->get_filename() << "\" \"" << _cert_dir << "\"";
|
||||
|
||||
// I'm not sure why CreateProcess wants a non-const char pointer for its
|
||||
// command-line string, but I'm not taking chances. It gets a non-const
|
||||
// char array that it can modify.
|
||||
string command_line_str = stream.str();
|
||||
char *command_line = new char[command_line_str.size() + 1];
|
||||
memcpy(command_line, command_line_str.c_str(), command_line_str.size() + 1);
|
||||
|
||||
nout << "Command line: " << command_line_str << "\n";
|
||||
|
||||
// Something about p3dCert_wx tends to become crashy when we call it from
|
||||
// CreateProcessW(). Something about the way wx parses the command-line
|
||||
// parameters? Well, whatever, we don't really need the Unicode form
|
||||
// anyway.
|
||||
PROCESS_INFORMATION process_info;
|
||||
BOOL result = CreateProcess
|
||||
(_p3dcert_exe.c_str(), command_line, nullptr, nullptr, TRUE,
|
||||
0, (void *)_env.c_str(),
|
||||
start_dir_cstr, &startup_info, &process_info);
|
||||
bool started_program = (result != 0);
|
||||
|
||||
delete[] command_line;
|
||||
|
||||
if (!started_program) {
|
||||
nout << "CreateProcess failed, error: " << GetLastError() << "\n";
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
CloseHandle(process_info.hThread);
|
||||
return process_info.hProcess;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
/**
|
||||
* Creates a sub-process to run _p3dcert_exe, with the appropriate command-
|
||||
* line arguments, and the environment string defined in _env.
|
||||
*
|
||||
* Returns the pid of the created process on success, or -1 on falure.
|
||||
*/
|
||||
int P3DAuthSession::
|
||||
posix_create_process() {
|
||||
// Fork and exec.
|
||||
pid_t child = fork();
|
||||
if (child < 0) {
|
||||
perror("fork");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (child == 0) {
|
||||
// Here we are in the child process.
|
||||
|
||||
if (chdir(_start_dir.c_str()) < 0) {
|
||||
nout << "Could not chdir to " << _start_dir << "\n";
|
||||
// This is a warning, not an error. We don't actually care that much
|
||||
// about the starting directory.
|
||||
}
|
||||
|
||||
// build up an array of char strings for the environment.
|
||||
std::vector<const char *> ptrs;
|
||||
size_t p = 0;
|
||||
size_t zero = _env.find('\0', p);
|
||||
while (zero != string::npos) {
|
||||
ptrs.push_back(_env.data() + p);
|
||||
p = zero + 1;
|
||||
zero = _env.find('\0', p);
|
||||
}
|
||||
ptrs.push_back(nullptr);
|
||||
|
||||
execle(_p3dcert_exe.c_str(), _p3dcert_exe.c_str(),
|
||||
_cert_filename->get_filename().c_str(), _cert_dir.c_str(),
|
||||
(char *)0, &ptrs[0]);
|
||||
nout << "Failed to exec " << _p3dcert_exe << "\n";
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
#endif // _WIN32
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dAuthSession.h
|
||||
* @author drose
|
||||
* @date 2009-09-17
|
||||
*/
|
||||
|
||||
#ifndef P3DAUTHSESSION_H
|
||||
#define P3DAUTHSESSION_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dPackage.h"
|
||||
#include "get_tinyxml.h"
|
||||
#include "p3dTemporaryFile.h"
|
||||
#include "p3dReferenceCount.h"
|
||||
|
||||
class P3DInstance;
|
||||
|
||||
/**
|
||||
* This is an instance of a p3dcert program running in a subprocess. There's
|
||||
* no communication with the process, or none of that complicated stuff the
|
||||
* P3DSession has to do; all we do here is fire off the process, then wait for
|
||||
* it to exit.
|
||||
*/
|
||||
class P3DAuthSession : public P3DReferenceCount {
|
||||
public:
|
||||
P3DAuthSession(P3DInstance *inst);
|
||||
~P3DAuthSession();
|
||||
|
||||
void shutdown(bool send_message);
|
||||
|
||||
private:
|
||||
void start_p3dcert();
|
||||
|
||||
void spawn_wait_thread();
|
||||
void join_wait_thread();
|
||||
|
||||
void write_env() const;
|
||||
|
||||
private:
|
||||
// These methods run only within the read thread.
|
||||
THREAD_CALLBACK_DECLARATION(P3DAuthSession, wt_thread_run);
|
||||
void wt_thread_run();
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE win_create_process();
|
||||
#else
|
||||
int posix_create_process();
|
||||
#endif
|
||||
|
||||
private:
|
||||
P3DInstance *_inst;
|
||||
std::string _start_dir;
|
||||
|
||||
// This information is passed to create_process().
|
||||
P3DTemporaryFile *_cert_filename;
|
||||
std::string _cert_dir;
|
||||
std::string _p3dcert_exe;
|
||||
std::string _env;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE _p3dcert_handle;
|
||||
#else
|
||||
int _p3dcert_pid;
|
||||
#endif
|
||||
bool _p3dcert_started;
|
||||
bool _p3dcert_running;
|
||||
|
||||
// The remaining members are manipulated by or for the read thread.
|
||||
bool _started_wait_thread;
|
||||
THREAD _wait_thread;
|
||||
};
|
||||
|
||||
#include "p3dAuthSession.I"
|
||||
|
||||
#endif
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dBoolObject.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#include "p3dBoolObject.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DBoolObject::
|
||||
P3DBoolObject(bool value) : _value(value) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DBoolObject::
|
||||
P3DBoolObject(const P3DBoolObject ©) :
|
||||
P3DObject(copy),
|
||||
_value(copy._value)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fundamental type of this kind of object.
|
||||
*/
|
||||
P3D_object_type P3DBoolObject::
|
||||
get_type() {
|
||||
return P3D_OT_bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a boolean, if possible.
|
||||
*/
|
||||
bool P3DBoolObject::
|
||||
get_bool() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to an integer, if possible.
|
||||
*/
|
||||
int P3DBoolObject::
|
||||
get_int() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the indicated C++ string object with the value of this object coerced
|
||||
* to a string.
|
||||
*/
|
||||
void P3DBoolObject::
|
||||
make_string(std::string &value) {
|
||||
if (_value) {
|
||||
value = "True";
|
||||
} else {
|
||||
value = "False";
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dBoolObject.h
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#ifndef P3DBOOLOBJECT_H
|
||||
#define P3DBOOLOBJECT_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dObject.h"
|
||||
|
||||
/**
|
||||
* An object type that contains a boolean value.
|
||||
*/
|
||||
class P3DBoolObject : public P3DObject {
|
||||
public:
|
||||
P3DBoolObject(bool value);
|
||||
P3DBoolObject(const P3DBoolObject ©);
|
||||
|
||||
public:
|
||||
virtual P3D_object_type get_type();
|
||||
virtual bool get_bool();
|
||||
virtual int get_int();
|
||||
virtual void make_string(std::string &value);
|
||||
|
||||
private:
|
||||
bool _value;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCInstance.I
|
||||
* @author drose
|
||||
* @date 2009-06-08
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a unique integer for each instance in the system.
|
||||
*/
|
||||
inline int P3DCInstance::
|
||||
get_instance_id() const {
|
||||
return _instance_id;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCInstance.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-08
|
||||
*/
|
||||
|
||||
#include "p3dCInstance.h"
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new Instance from an XML description.
|
||||
*/
|
||||
P3DCInstance::
|
||||
P3DCInstance(TiXmlElement *xinstance) :
|
||||
_func(nullptr)
|
||||
{
|
||||
xinstance->Attribute("instance_id", &_instance_id);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DCInstance::
|
||||
~P3DCInstance() {
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCInstance.h
|
||||
* @author drose
|
||||
* @date 2009-06-08
|
||||
*/
|
||||
|
||||
#ifndef P3DCINSTANCE_H
|
||||
#define P3DCINSTANCE_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#include "p3d_plugin.h"
|
||||
#include "pvector.h"
|
||||
#include "get_tinyxml.h"
|
||||
#include "windowHandle.h"
|
||||
|
||||
class P3DSession;
|
||||
|
||||
/**
|
||||
* This is an instance of a Panda3D window, as seen in the child-level
|
||||
* process.
|
||||
*/
|
||||
class P3DCInstance : public P3D_instance {
|
||||
public:
|
||||
P3DCInstance(TiXmlElement *xinstance);
|
||||
~P3DCInstance();
|
||||
|
||||
inline int get_instance_id() const;
|
||||
|
||||
public:
|
||||
PT(WindowHandle) _parent_window_handle;
|
||||
|
||||
private:
|
||||
P3D_request_ready_func *_func;
|
||||
|
||||
int _instance_id;
|
||||
|
||||
friend class P3DPythonRun;
|
||||
};
|
||||
|
||||
#include "p3dCInstance.I"
|
||||
|
||||
#endif
|
||||
@@ -1,742 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCert.cxx
|
||||
* @author rdb
|
||||
* @date 2011-03-08
|
||||
*/
|
||||
|
||||
#include "p3dCert.h"
|
||||
#include "p3dCert_strings.h"
|
||||
#include "wstring_encode.h"
|
||||
#include "mkdir_complete.h"
|
||||
|
||||
#include <FL/Fl_Box.H>
|
||||
#include <FL/Fl_Button.H>
|
||||
#include <FL/Fl_Return_Button.H>
|
||||
#include <FL/Fl_Text_Display.H>
|
||||
|
||||
#include <cassert>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
|
||||
#define BUTTON_WIDTH 180 // fit the Russian text
|
||||
#define BUTTON_SPACE 10
|
||||
|
||||
#include "ca_bundle_data_src.c"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#define snprintf sprintf_s
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
using std::cerr;
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
|
||||
static LanguageIndex li = LI_default;
|
||||
|
||||
#if defined(_WIN32)
|
||||
static LanguageIndex detect_language() {
|
||||
// This function was introduced in Windows Vista; it may not be available on
|
||||
// older systems.
|
||||
typedef BOOL (*GUPL)(DWORD, PULONG, PZZWSTR, PULONG);
|
||||
GUPL pGetUserPreferredUILanguages = (GUPL)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
|
||||
TEXT("GetUserPreferredUILanguages"));
|
||||
if (pGetUserPreferredUILanguages != nullptr) {
|
||||
ULONG num_langs = 0;
|
||||
ULONG buffer_size = 0;
|
||||
pGetUserPreferredUILanguages(8, &num_langs, nullptr, &buffer_size);
|
||||
PZZWSTR buffer = (PZZWSTR)_alloca(buffer_size);
|
||||
if (pGetUserPreferredUILanguages(8, &num_langs, buffer, &buffer_size)) {
|
||||
for (ULONG i = 0; i < num_langs; ++i) {
|
||||
size_t len = wcslen(buffer);
|
||||
if (len >= 2 && (buffer[2] == 0 || buffer[2] == L'-')) {
|
||||
// It may be a two-letter code; match it in our list.
|
||||
for (int j = 0; j < LI_COUNT; ++j) {
|
||||
const char *lang_code = language_codes[j];
|
||||
if (lang_code != nullptr && lang_code[0] == buffer[0] &&
|
||||
lang_code[1] == buffer[1]) {
|
||||
return (LanguageIndex)j;
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer += len + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to the old Windows XP function.
|
||||
LANGID lang = GetUserDefaultUILanguage() & 0x3ff;
|
||||
if (lang == 0) {
|
||||
return LI_default;
|
||||
}
|
||||
|
||||
for (int i = 0; i < LI_COUNT; ++i) {
|
||||
if (language_ids[i] != 0 && language_ids[i] == lang) {
|
||||
return (LanguageIndex)i;
|
||||
}
|
||||
}
|
||||
return LI_default;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
static LanguageIndex detect_language() {
|
||||
// Get and iterate through the list of preferred languages.
|
||||
CFArrayRef langs = CFLocaleCopyPreferredLanguages();
|
||||
CFIndex num_langs = CFArrayGetCount(langs);
|
||||
|
||||
for (long i = 0; i < num_langs; ++i) {
|
||||
CFStringRef lang = (CFStringRef)CFArrayGetValueAtIndex(langs, i);
|
||||
|
||||
CFIndex length = CFStringGetLength(lang);
|
||||
if (length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
||||
char *buffer = (char *)alloca(max_size);
|
||||
if (!CFStringGetCString(lang, buffer, max_size, kCFStringEncodingUTF8)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isalnum(buffer[2])) {
|
||||
// It's not a two-letter code.
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if we support this language.
|
||||
for (int j = 0; j < LI_COUNT; ++j) {
|
||||
const char *lang_code = language_codes[j];
|
||||
if (lang_code != nullptr && strncasecmp(buffer, lang_code, 2) == 0) {
|
||||
CFRelease(langs);
|
||||
return (LanguageIndex)j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(langs);
|
||||
return LI_default;
|
||||
}
|
||||
|
||||
#else
|
||||
static LanguageIndex detect_language() {
|
||||
// First consult the LANGUAGE variable, which is a GNU extension that can
|
||||
// contain multiple languages in order of preference.
|
||||
const char *lang = getenv("LANGUAGE");
|
||||
while (lang != nullptr && lang[0] != 0) {
|
||||
size_t len;
|
||||
const char *next = strchr(lang, ':');
|
||||
if (next == nullptr) {
|
||||
len = strlen(lang);
|
||||
} else {
|
||||
len = (next - lang);
|
||||
++next;
|
||||
}
|
||||
|
||||
if (len >= 2 && !isalnum(lang[2])) {
|
||||
// It may be a two-letter language code; match it in our list.
|
||||
for (int i = 0; i < LI_COUNT; ++i) {
|
||||
const char *lang_code = language_codes[i];
|
||||
if (lang_code != nullptr && strncasecmp(lang, lang_code, 2) == 0) {
|
||||
return (LanguageIndex)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lang = next;
|
||||
}
|
||||
|
||||
// Fall back to the C locale functions.
|
||||
setlocale(LC_ALL, "");
|
||||
lang = setlocale(LC_MESSAGES, nullptr);
|
||||
|
||||
if (lang == nullptr || lang[0] == 0 || strcmp(lang, "C") == 0) {
|
||||
// Try the LANG variable.
|
||||
lang = getenv("LANG");
|
||||
}
|
||||
|
||||
if (lang == nullptr || strlen(lang) < 2 || isalnum(lang[2])) {
|
||||
// Couldn't extract a meaningful two-letter code.
|
||||
return LI_default;
|
||||
}
|
||||
|
||||
// It may be a two-letter language code; match it in our list.
|
||||
for (int i = 0; i < LI_COUNT; ++i) {
|
||||
const char *lang_code = language_codes[i];
|
||||
if (lang_code != nullptr && strncasecmp(lang, lang_code, 2) == 0) {
|
||||
return (LanguageIndex)i;
|
||||
}
|
||||
}
|
||||
return LI_default;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
int WINAPI
|
||||
wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
LPWSTR *argv;
|
||||
int argc;
|
||||
argv = CommandLineToArgvW(pCmdLine, &argc);
|
||||
if (argv == nullptr || argc != 2) {
|
||||
cerr << "usage: p3dcert cert_filename cert_dir\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
li = detect_language();
|
||||
|
||||
wstring cert_filename (argv[0]);
|
||||
wstring cert_dir (argv[1]);
|
||||
LocalFree(argv);
|
||||
|
||||
AuthDialog *dialog = new AuthDialog(cert_filename, cert_dir);
|
||||
dialog->show();
|
||||
|
||||
return Fl::run();
|
||||
}
|
||||
|
||||
#else // _WIN32
|
||||
int main(int argc, char **argv) {
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
if (argc != 3) {
|
||||
cerr << "usage: p3dcert cert_filename cert_dir\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
li = detect_language();
|
||||
|
||||
string cert_filename (argv[1]);
|
||||
string cert_dir (argv[2]);
|
||||
|
||||
AuthDialog *dialog = new AuthDialog(cert_filename, cert_dir);
|
||||
dialog->show(1, argv);
|
||||
|
||||
return Fl::run();
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
AuthDialog::
|
||||
AuthDialog(const wstring &cert_filename, const wstring &cert_dir) :
|
||||
#else
|
||||
AuthDialog::
|
||||
AuthDialog(const string &cert_filename, const string &cert_dir) :
|
||||
#endif
|
||||
Fl_Window(435, 242, new_application_title[li]),
|
||||
_cert_dir(cert_dir)
|
||||
{
|
||||
_view_cert_dialog = nullptr;
|
||||
|
||||
_cert = nullptr;
|
||||
_stack = nullptr;
|
||||
_verify_result = -1;
|
||||
|
||||
// Center the window on the screen.
|
||||
position((Fl::w() - w()) / 2, (Fl::h() - h()) / 2);
|
||||
set_modal();
|
||||
|
||||
read_cert_file(cert_filename);
|
||||
get_friendly_name();
|
||||
verify_cert();
|
||||
layout();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
AuthDialog::
|
||||
~AuthDialog() {
|
||||
if (_view_cert_dialog != nullptr) {
|
||||
_view_cert_dialog->hide();
|
||||
}
|
||||
|
||||
if (_cert != nullptr) {
|
||||
X509_free(_cert);
|
||||
_cert = nullptr;
|
||||
}
|
||||
if (_stack != nullptr) {
|
||||
sk_X509_free(_stack);
|
||||
_stack = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Run" button.
|
||||
*/
|
||||
void AuthDialog::
|
||||
run_clicked(Fl_Widget *w, void *data) {
|
||||
AuthDialog *dlg = (AuthDialog *) data;
|
||||
dlg->approve_cert();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "View Certificate" button.
|
||||
*/
|
||||
void AuthDialog::
|
||||
view_cert_clicked(Fl_Widget *w, void *data) {
|
||||
AuthDialog *dlg = (AuthDialog *) data;
|
||||
|
||||
if (dlg->_view_cert_dialog != nullptr) {
|
||||
dlg->_view_cert_dialog->hide();
|
||||
}
|
||||
dlg->hide();
|
||||
dlg->_view_cert_dialog = new ViewCertDialog(dlg, dlg->_cert);
|
||||
dlg->_view_cert_dialog->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Cancel" button.
|
||||
*/
|
||||
void AuthDialog::
|
||||
cancel_clicked(Fl_Widget *w, void *data) {
|
||||
AuthDialog *dlg = (AuthDialog *) data;
|
||||
dlg->hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the certificate into the _cert_dir, so that it will be found by the
|
||||
* P3DInstanceManager and known to be approved.
|
||||
*/
|
||||
void AuthDialog::
|
||||
approve_cert() {
|
||||
assert(_cert != nullptr);
|
||||
|
||||
// Make sure the directory exists.
|
||||
#ifdef _WIN32
|
||||
mkdir_complete_w(_cert_dir, cerr);
|
||||
#else
|
||||
mkdir_complete(_cert_dir, cerr);
|
||||
#endif
|
||||
|
||||
// Look for an unused filename.
|
||||
int i = 1;
|
||||
size_t buf_length = _cert_dir.length() + 100;
|
||||
|
||||
// Sure, there's a slight race condition right now: another process might
|
||||
// attempt to create the same filename. So what.
|
||||
FILE *fp = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
wchar_t *buf = new wchar_t[buf_length];
|
||||
|
||||
while (true) {
|
||||
swprintf(buf, L"%s/p%d.crt", _cert_dir.c_str(), i);
|
||||
assert(wcslen(buf) < buf_length);
|
||||
|
||||
// Check if it already exists. If not, take it.
|
||||
if (GetFileAttributesW(buf) == -1) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
fp = _wfopen(buf, L"w");
|
||||
|
||||
#else // _WIN32
|
||||
char *buf = new char[buf_length];
|
||||
|
||||
while (true) {
|
||||
sprintf(buf, "%s/p%d.crt", _cert_dir.c_str(), i);
|
||||
assert(strlen(buf) < buf_length);
|
||||
|
||||
// Check if it already exists. If not, take it.
|
||||
struct stat statbuf;
|
||||
if (stat(buf, &statbuf) != 0) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
fp = fopen(buf, "w");
|
||||
#endif // _WIN32
|
||||
|
||||
if (fp != nullptr) {
|
||||
PEM_write_X509(fp, _cert);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the list of certificates in the pem filename passed on the command
|
||||
* line into _cert and _stack.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
void AuthDialog::
|
||||
read_cert_file(const wstring &cert_filename) {
|
||||
#else
|
||||
void AuthDialog::
|
||||
read_cert_file(const string &cert_filename) {
|
||||
#endif
|
||||
|
||||
FILE *fp = nullptr;
|
||||
#ifdef _WIN32
|
||||
fp = _wfopen(cert_filename.c_str(), L"r");
|
||||
#else // _WIN32
|
||||
fp = fopen(cert_filename.c_str(), "r");
|
||||
#endif // _WIN32
|
||||
|
||||
if (fp == nullptr) {
|
||||
#ifdef _WIN32
|
||||
wcerr << L"Couldn't read " << cert_filename.c_str() << L"\n";
|
||||
#else
|
||||
cerr << "Couldn't read " << cert_filename.c_str() << "\n";
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
_cert = PEM_read_X509(fp, nullptr, nullptr, (void *)"");
|
||||
if (_cert == nullptr) {
|
||||
#ifdef _WIN32
|
||||
wcerr << L"Could not read certificate in " << cert_filename.c_str() << L".\n";
|
||||
#else
|
||||
cerr << "Could not read certificate in " << cert_filename.c_str() << ".\n";
|
||||
#endif
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build up a STACK of the remaining certificates in the file.
|
||||
_stack = sk_X509_new(nullptr);
|
||||
X509 *c = PEM_read_X509(fp, nullptr, nullptr, (void *)"");
|
||||
while (c != nullptr) {
|
||||
sk_X509_push(_stack, c);
|
||||
c = PEM_read_X509(fp, nullptr, nullptr, (void *)"");
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the "friendly name" from the certificate: the common name or email
|
||||
* name.
|
||||
*/
|
||||
void AuthDialog::
|
||||
get_friendly_name() {
|
||||
if (_cert == nullptr) {
|
||||
_friendly_name.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static const int nid_choices[] = {
|
||||
NID_pkcs9_emailAddress,
|
||||
NID_commonName,
|
||||
-1,
|
||||
};
|
||||
|
||||
// Choose the first NID that exists on the cert.
|
||||
for (int ni = 0; nid_choices[ni] != -1; ++ni) {
|
||||
int nid = nid_choices[ni];
|
||||
|
||||
// A complex OpenSSL interface to extract out the name in utf-8.
|
||||
X509_NAME *xname = X509_get_subject_name(_cert);
|
||||
if (xname != nullptr) {
|
||||
int pos = X509_NAME_get_index_by_NID(xname, nid, -1);
|
||||
if (pos != -1) {
|
||||
// We just get the first common name. I guess it's possible to have
|
||||
// more than one; not sure what that means in this context.
|
||||
X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos);
|
||||
if (xentry != nullptr) {
|
||||
ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry);
|
||||
if (data != nullptr) {
|
||||
// We use "print" to dump the output to a memory BIO. Is there an
|
||||
// easier way to decode the ASN1_STRING? Curse these incomplete
|
||||
// docs.
|
||||
BIO *mbio = BIO_new(BIO_s_mem());
|
||||
ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB);
|
||||
|
||||
char *pp;
|
||||
long pp_size = BIO_get_mem_data(mbio, &pp);
|
||||
_friendly_name = string(pp, pp_size);
|
||||
BIO_free(mbio);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the certificate is valid by the chain and initializes
|
||||
* _verify_status accordingly.
|
||||
*/
|
||||
void AuthDialog::
|
||||
verify_cert() {
|
||||
if (_cert == nullptr) {
|
||||
_verify_result = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new X509_STORE.
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
X509_STORE_set_default_paths(store);
|
||||
|
||||
// Add in the well-known certificate authorities.
|
||||
load_certificates_from_der_ram(store, (const char *)ca_bundle_data, ca_bundle_data_len);
|
||||
|
||||
// Create the X509_STORE_CTX for verifying the cert and chain.
|
||||
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
|
||||
X509_STORE_CTX_init(ctx, store, _cert, _stack);
|
||||
X509_STORE_CTX_set_cert(ctx, _cert);
|
||||
|
||||
if (X509_verify_cert(ctx)) {
|
||||
_verify_result = 0;
|
||||
} else {
|
||||
_verify_result = X509_STORE_CTX_get_error(ctx);
|
||||
}
|
||||
|
||||
X509_STORE_CTX_free(ctx);
|
||||
|
||||
X509_STORE_free(store);
|
||||
|
||||
cerr << "Got certificate from " << _friendly_name.c_str()
|
||||
<< ", verify_result = " << _verify_result << "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a chain of trusted certificates from the indicated data buffer and
|
||||
* adds them to the X509_STORE object. The data buffer should be DER-
|
||||
* formatted. Returns the number of certificates read on success, or 0 on
|
||||
* failure.
|
||||
*
|
||||
* You should call this only with trusted, locally-stored certificates; not
|
||||
* with certificates received from an untrusted source.
|
||||
*/
|
||||
int AuthDialog::
|
||||
load_certificates_from_der_ram(X509_STORE *store,
|
||||
const char *data, size_t data_size) {
|
||||
int count = 0;
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
|
||||
// Beginning in 0.9.8, d2i_X509() accepted a const unsigned char **.
|
||||
const unsigned char *bp, *bp_end;
|
||||
#else
|
||||
// Prior to 0.9.8, d2i_X509() accepted an unsigned char **.
|
||||
unsigned char *bp, *bp_end;
|
||||
#endif
|
||||
|
||||
bp = (unsigned char *)data;
|
||||
bp_end = bp + data_size;
|
||||
X509 *x509 = d2i_X509(nullptr, &bp, bp_end - bp);
|
||||
while (x509 != nullptr) {
|
||||
X509_STORE_add_cert(store, x509);
|
||||
++count;
|
||||
x509 = d2i_X509(nullptr, &bp, bp_end - bp);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges the text and controls within the dialog.
|
||||
*/
|
||||
void AuthDialog::
|
||||
layout() {
|
||||
get_text(_header, sizeof _header, _text, sizeof _text);
|
||||
|
||||
// Now replicate out any @ signs in the text to avoid FlTk's escape
|
||||
// sequences.
|
||||
int j = 0;
|
||||
for (int i = 0; _text[i] != '\0'; ++i) {
|
||||
_text_clean[j++] = _text[i];
|
||||
if (_text[i] == '@') {
|
||||
_text_clean[j++] = _text[i];
|
||||
}
|
||||
}
|
||||
_text_clean[j] = '\0';
|
||||
assert(strlen(_text_clean) < sizeof(_text_clean));
|
||||
|
||||
int next_y = 35;
|
||||
if (strlen(_header) > 0) {
|
||||
Fl_Box *text0 = new Fl_Box(w() / 2, next_y, 0, 25, _header);
|
||||
text0->align(FL_ALIGN_TOP | FL_ALIGN_CENTER);
|
||||
text0->labelfont(FL_BOLD);
|
||||
text0->labelsize(text0->labelsize() * 1.5);
|
||||
next_y += 25;
|
||||
}
|
||||
|
||||
Fl_Box *text1 = new Fl_Box(17, next_y, 400, 180, _text_clean);
|
||||
text1->align(FL_ALIGN_TOP | FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
|
||||
next_y += 180;
|
||||
|
||||
short nbuttons = 1;
|
||||
if (_cert != nullptr) {
|
||||
nbuttons++;
|
||||
if (_verify_result == 0) {
|
||||
nbuttons++;
|
||||
}
|
||||
}
|
||||
short bx = (w() - nbuttons * BUTTON_WIDTH - (nbuttons - 1) * BUTTON_SPACE) / 2;
|
||||
|
||||
if (_verify_result == 0 && _cert != nullptr) {
|
||||
Fl_Return_Button *run_button = new Fl_Return_Button(bx, next_y, BUTTON_WIDTH, 25, run_title[li]);
|
||||
run_button->callback(this->run_clicked, this);
|
||||
bx += BUTTON_WIDTH + BUTTON_SPACE;
|
||||
}
|
||||
|
||||
if (_cert != nullptr) {
|
||||
Fl_Button *view_button = new Fl_Button(bx, next_y, BUTTON_WIDTH, 25, show_cert_title[li]);
|
||||
view_button->callback(this->view_cert_clicked, this);
|
||||
bx += BUTTON_WIDTH + BUTTON_SPACE;
|
||||
}
|
||||
|
||||
Fl_Button *cancel_button;
|
||||
cancel_button = new Fl_Button(bx, next_y, BUTTON_WIDTH, 25, cancel_title[li]);
|
||||
cancel_button->callback(this->cancel_clicked, this);
|
||||
|
||||
next_y += 42;
|
||||
size(435, next_y);
|
||||
|
||||
end();
|
||||
set_modal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the text appropriate to display in the dialog box, based on the
|
||||
* certificate read so far.
|
||||
*/
|
||||
void AuthDialog::
|
||||
get_text(char *header, size_t hlen, char *text, size_t tlen) {
|
||||
switch (_verify_result) {
|
||||
case -1:
|
||||
strncpy(header, no_cert_title[li], hlen);
|
||||
strncpy(text, no_cert_text[li], tlen);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
snprintf(text, tlen, verified_cert_text[li], _friendly_name.c_str(),
|
||||
_friendly_name.c_str(), _friendly_name.c_str());
|
||||
break;
|
||||
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
case X509_V_ERR_CRL_NOT_YET_VALID:
|
||||
case X509_V_ERR_CRL_HAS_EXPIRED:
|
||||
strncpy(header, expired_cert_title[li], hlen);
|
||||
snprintf(text, tlen, expired_cert_text[li], _friendly_name.c_str());
|
||||
break;
|
||||
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
strncpy(header, unverified_cert_title[li], hlen);
|
||||
strncpy(text, unknown_auth_cert_text[li], tlen);
|
||||
break;
|
||||
|
||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
strncpy(header, unverified_cert_title[li], hlen);
|
||||
strncpy(text, self_signed_cert_text[li], tlen);
|
||||
break;
|
||||
|
||||
default:
|
||||
strncpy(header, unverified_cert_title[li], hlen);
|
||||
snprintf(text, tlen, generic_error_cert_text[li], _verify_result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ViewCertDialog::
|
||||
ViewCertDialog(AuthDialog *auth_dialog, X509 *cert) :
|
||||
Fl_Window(600, 400, show_cert_title[li]),
|
||||
_auth_dialog(auth_dialog),
|
||||
_cert(cert)
|
||||
{
|
||||
// Center the window on the screen.
|
||||
position((Fl::w() - w()) / 2, (Fl::h() - h()) / 2);
|
||||
set_modal();
|
||||
|
||||
layout();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ViewCertDialog::
|
||||
~ViewCertDialog() {
|
||||
if (_auth_dialog != nullptr) {
|
||||
_auth_dialog->_view_cert_dialog = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Run" button.
|
||||
*/
|
||||
void ViewCertDialog::
|
||||
run_clicked(Fl_Widget *w, void *data) {
|
||||
ViewCertDialog *dlg = (ViewCertDialog *) data;
|
||||
if (dlg->_auth_dialog != nullptr){
|
||||
dlg->_auth_dialog->approve_cert();
|
||||
}
|
||||
dlg->hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Cancel" button.
|
||||
*/
|
||||
void ViewCertDialog::
|
||||
cancel_clicked(Fl_Widget *w, void *data) {
|
||||
ViewCertDialog *dlg = (ViewCertDialog *) data;
|
||||
if (dlg->_auth_dialog != nullptr){
|
||||
dlg->_auth_dialog->hide();
|
||||
}
|
||||
dlg->hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges the text and controls within the dialog.
|
||||
*/
|
||||
void ViewCertDialog::
|
||||
layout() {
|
||||
// Format the certificate text for display in the dialog.
|
||||
assert(_cert != nullptr);
|
||||
|
||||
BIO *mbio = BIO_new(BIO_s_mem());
|
||||
X509_print(mbio, _cert);
|
||||
|
||||
char *pp;
|
||||
long pp_size = BIO_get_mem_data(mbio, &pp);
|
||||
string cert_body(pp, pp_size);
|
||||
BIO_free(mbio);
|
||||
|
||||
Fl_Text_Buffer *buffer = new Fl_Text_Buffer;
|
||||
buffer->append(cert_body.c_str());
|
||||
|
||||
Fl_Text_Display *text = new Fl_Text_Display(20, 20, 565, 320);
|
||||
text->buffer(buffer);
|
||||
|
||||
short bx = (w() - BUTTON_WIDTH * 2 - BUTTON_SPACE) / 2;
|
||||
|
||||
Fl_Return_Button *run_button = new Fl_Return_Button(bx, 360, BUTTON_WIDTH, 25, run_title[li]);
|
||||
run_button->callback(this->run_clicked, this);
|
||||
|
||||
bx += BUTTON_WIDTH + BUTTON_SPACE;
|
||||
|
||||
Fl_Button *cancel_button = new Fl_Button(bx, 360, BUTTON_WIDTH, 25, cancel_title[li]);
|
||||
cancel_button->callback(this->cancel_clicked, this);
|
||||
|
||||
end();
|
||||
set_modal();
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCert.h
|
||||
* @author rdb
|
||||
* @date 2011-03-08
|
||||
*/
|
||||
|
||||
#ifndef P3DCERT_H
|
||||
#define P3DCERT_H
|
||||
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Window.H>
|
||||
|
||||
#define OPENSSL_NO_KRB5
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
class ViewCertDialog;
|
||||
|
||||
#ifndef STACK_OF
|
||||
// At some point, presumably in 1.0.0, openssl went to the STACK_OF() macro
|
||||
// system to typedef the contents of a stack. Unfortunately, that new API
|
||||
// is different. We define some macros here here for backward compatiblity.
|
||||
#define STACK_OF(type) STACK
|
||||
#define sk_X509_push(stack, item) sk_push((stack), (char *)(item))
|
||||
#define sk_X509_free(stack) sk_free(stack)
|
||||
#define sk_X509_new(cmp) sk_new(cmp)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is the primary dialog of this application.
|
||||
*
|
||||
* This dialog is presented to the user when he/she clicks on the red
|
||||
* authorization button on the splash window. It tells the user the status of
|
||||
* the application's signature, and invites the user to approve the signature
|
||||
* or cancel.
|
||||
*/
|
||||
class AuthDialog : public Fl_Window {
|
||||
public:
|
||||
#ifdef _WIN32
|
||||
AuthDialog(const std::wstring &cert_filename, const std::wstring &cert_dir);
|
||||
#else
|
||||
AuthDialog(const std::string &cert_filename, const std::string &cert_dir);
|
||||
#endif
|
||||
virtual ~AuthDialog();
|
||||
|
||||
static void run_clicked(Fl_Widget *w, void *data);
|
||||
static void view_cert_clicked(Fl_Widget *w, void *data);
|
||||
static void cancel_clicked(Fl_Widget *w, void *data);
|
||||
|
||||
void approve_cert();
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
void read_cert_file(const std::wstring &cert_filename);
|
||||
#else
|
||||
void read_cert_file(const std::string &cert_filename);
|
||||
#endif
|
||||
void get_friendly_name();
|
||||
void verify_cert();
|
||||
int load_certificates_from_der_ram(X509_STORE *store,
|
||||
const char *data, size_t data_size);
|
||||
|
||||
void layout();
|
||||
void get_text(char *header, size_t hlen, char *text, size_t tlen);
|
||||
|
||||
public:
|
||||
ViewCertDialog *_view_cert_dialog;
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
std::wstring _cert_dir;
|
||||
#else
|
||||
std::string _cert_dir;
|
||||
#endif
|
||||
X509 *_cert;
|
||||
STACK_OF(X509) *_stack;
|
||||
|
||||
char _header[64];
|
||||
char _text[1024];
|
||||
char _text_clean[2048];
|
||||
|
||||
std::string _friendly_name;
|
||||
int _verify_result;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the detailed view of the particular certificate.
|
||||
*/
|
||||
class ViewCertDialog : public Fl_Window {
|
||||
public:
|
||||
ViewCertDialog(AuthDialog *auth_dialog, X509 *cert);
|
||||
virtual ~ViewCertDialog();
|
||||
|
||||
static void run_clicked(Fl_Widget *w, void *data);
|
||||
static void cancel_clicked(Fl_Widget *w, void *data);
|
||||
|
||||
private:
|
||||
void layout();
|
||||
|
||||
private:
|
||||
AuthDialog *_auth_dialog;
|
||||
X509 *_cert;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,567 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCert_strings.cxx
|
||||
* @author rdb
|
||||
* @date 2015-03-25
|
||||
*/
|
||||
|
||||
#include "p3dCert_strings.h"
|
||||
|
||||
// Translations kindly provided by: eng: drwr nld: rdb deu: Sebastian Hoffmann
|
||||
// <TheCheapestPixels at googlemail dot com> spa: Imanol Celaya <imanol at
|
||||
// celaya dot me> ita: Flavio Clava rus: montreal
|
||||
|
||||
const char *language_codes[LI_COUNT] =
|
||||
{"en", "nl", "de", "es", "it", "eo", "ru"};
|
||||
|
||||
// https:msdn.microsoft.comen-
|
||||
// uslibrarywindowsdesktopdd318693%28v=vs.85%29.aspx
|
||||
const unsigned char language_ids[LI_COUNT] =
|
||||
{0x09, 0x13, 0x07, 0x0A, 0x10, 0x8F, 0x19};
|
||||
|
||||
const char *
|
||||
run_title[LI_COUNT] = {
|
||||
"Run",
|
||||
"Uitvoeren",
|
||||
"Starten",
|
||||
"Ejecutar",
|
||||
"Lancia",
|
||||
"Lan\304\211i",
|
||||
"\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214",
|
||||
};
|
||||
|
||||
const char *
|
||||
cancel_title[LI_COUNT] = {
|
||||
"Cancel",
|
||||
"Annuleren",
|
||||
"Abbrechen",
|
||||
"Cancelar",
|
||||
"Cancella",
|
||||
"Nuligi",
|
||||
"\320\236\321\202\320\274\320\265\320\275\320\260",
|
||||
};
|
||||
|
||||
const char *
|
||||
show_cert_title[LI_COUNT] = {
|
||||
"Show Certificate",
|
||||
"Toon Certificaat",
|
||||
"Zertifikat anzeigen",
|
||||
"Mostrar certificado",
|
||||
"Mostra certificato",
|
||||
"Montri Ateston",
|
||||
"\320\237\320\276\320\272\320\260\320\267\320\260\321\202\321\214 \321\201"
|
||||
"\320\265\321\200\321\202\320\270\321\204\320\270\320\272\320\260\321\202",
|
||||
};
|
||||
|
||||
const char *
|
||||
new_application_title[LI_COUNT] = {
|
||||
"New Panda3D Application",
|
||||
"Nieuwe Panda3D Applicatie",
|
||||
"Neue Panda3D-Anwendung",
|
||||
"Nueva aplicaci\303\263n de Panda3D",
|
||||
"Nuova applicazione Panda3D",
|
||||
"Nova aplika\304\265o de Panda3D",
|
||||
"\320\235\320\276\320\262\320\276\320\265 Panda3D-\320\277\321\200\320\270"
|
||||
"\320\273\320\276\320\266\320\265\320\275\320\270\320\265",
|
||||
};
|
||||
|
||||
const char *
|
||||
no_cert_title[LI_COUNT] = {
|
||||
"No signature!",
|
||||
"Geen handtekening!",
|
||||
"Keine Signatur!",
|
||||
"Sin firma!",
|
||||
"Nessuna firma!",
|
||||
"Neniu subskribo!",
|
||||
"\320\235\320\265\321\202 \320\277\320\276\320\264\320\277\320\270\321\201"
|
||||
"\320\270!",
|
||||
};
|
||||
|
||||
const char *
|
||||
unverified_cert_title[LI_COUNT] = {
|
||||
"Unverified signature!",
|
||||
"Ongeverifieerde handtekening!",
|
||||
"Unbest\303\244tigte Signatur!",
|
||||
"Firma sin verificar!",
|
||||
"Firma non verificata!",
|
||||
"Nekontrolita subskribo!",
|
||||
"\320\235\320\265\320\277\321\200\320\276\320\262\320\265\321\200\320\265"
|
||||
"\320\275\320\275\320\260\321\217 \320\277\320\276\320\264\320\277\320\270"
|
||||
"\321\201\321\214!",
|
||||
};
|
||||
|
||||
const char *
|
||||
expired_cert_title[LI_COUNT] = {
|
||||
"Expired signature!",
|
||||
"Verlopen handtekening!",
|
||||
"Abgelaufene Signatur!",
|
||||
"Firma caducada!",
|
||||
"Firma scaduta!",
|
||||
"Eksvalidi\304\235inta subskribo!",
|
||||
"\320\241\321\200\320\276\320\272 \320\264\320\265\320\271\321\201\321\202"
|
||||
"\320\262\320\270\321\217 \320\277\320\276\320\264\320\277\320\270\321\201"
|
||||
"\320\270 \320\270\321\201\321\202\321\221\320\272!",
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
self_signed_cert_text[LI_COUNT] = {
|
||||
// eng
|
||||
"This Panda3D application uses a self-signed certificate. "
|
||||
"This means the author's name can't be verified, and you have "
|
||||
"no way of knowing for sure who wrote it.\n"
|
||||
"\n"
|
||||
"We recommend you click Cancel to avoid running this application.",
|
||||
|
||||
// nld
|
||||
"Deze Panda3D applicatie gebruikt een zelf-getekend certificaat. "
|
||||
"Dit betekent dat de auteursnaam niet kan worden geverifieerd, en het "
|
||||
"niet zeker is of de applicatie te vertrouwen is.\n"
|
||||
"\n"
|
||||
"Het is aanbevolen om op Annuleren te klikken om de applicatie af te "
|
||||
"sluiten.",
|
||||
|
||||
// deu
|
||||
"Diese Panda3D-Anwendung benutzt ein selbst-signiertes Zertifikat. Dies "
|
||||
"bedeutet, dass weder der Name des Autors \303\274berpr\303\274ft werden "
|
||||
"kann, noch dass garantiert werden kann, dass tats\303\244chlich die "
|
||||
"angegebene Person diese Anwendung geschrieben hat.\n"
|
||||
"\n"
|
||||
"Wir empfehlen, dass Sie Abbrechen dr\303\274cken um diese Anwendung nicht "
|
||||
"auszuf\303\274hren.",
|
||||
|
||||
// spa
|
||||
"Esta aplicaci\303\263n de Panda3D usa un certificado autofirmado. "
|
||||
"Esto significa que el nombre del autor no puede ser verificado y no se "
|
||||
"puede conocer seguro quien la ha escrito.\n"
|
||||
"\n"
|
||||
"Se recomienda cancelar para evitar ejecutar esta aplicaci\303\263n.",
|
||||
|
||||
// ita
|
||||
"Questa applicazione Panda3D usa un certificato autofirmato. Ci\303\262 "
|
||||
"significa che il nome dell'autore non pu\303\262 essere verificato, e "
|
||||
"che non hai modo di assicurarti circa chi la abbia scritta.\n"
|
||||
"\n"
|
||||
"Raccomandiamo di cliccare su Cancella per evitare di lanciare questa "
|
||||
"applicazione.",
|
||||
|
||||
// epo
|
||||
"\304\210i tiu aplika\304\265o de Panda3D uzas memsubskribitan ateston. "
|
||||
"Tio signifas ke la nomo de la verkanto ne povas esti kontrolita, kaj vi "
|
||||
"ne havas certan scimanieron pri la vera verkanto de la aplika\304\265o.\n"
|
||||
"\n"
|
||||
"Ni rekomendas ke vi premas la butonon 'Nuligi' por eviti lan\304\211on de "
|
||||
"\304\211i tiu aplika\304\265o.",
|
||||
|
||||
// rus
|
||||
"\320\255\321\202\320\276 \320\277\321\200\320\270\320\273\320\276\320\266"
|
||||
"\320\265\320\275\320\270\320\265 \320\270\321\201\320\277\320\276\320\273"
|
||||
"\321\214\320\267\321\203\320\265\321\202 \321\201\320\260\320\274\320\276"
|
||||
"\320\267\320\260\320\262\320\265\321\200\320\265\320\275\320\275\321\213"
|
||||
"\320\271 \321\201\320\265\321\200\321\202\320\270\321\204\320\270\320\272"
|
||||
"\320\260\321\202. \320\255\321\202\320\276 \320\276\320\267\320\275"
|
||||
"\320\260\321\207\320\260\320\265\321\202, \321\207\321\202\320\276 "
|
||||
"\320\270\320\274\321\217 \320\260\320\262\321\202\320\276\321\200\320\260 "
|
||||
"\320\275\320\265 \320\274\320\276\320\266\320\265\321\202 \320\261\321\213"
|
||||
"\321\202\321\214 \320\277\320\276\320\264\321\202\320\262\320\265\320\266"
|
||||
"\320\264\320\265\320\275\320\276, \320\270 \320\262\321\213 \320\275"
|
||||
"\320\265 \320\274\320\276\320\266\320\265\321\202\320\265 \320\267\320\275"
|
||||
"\320\260\321\202\321\214, \320\272\321\202\320\276 \320\275\320\260"
|
||||
"\320\277\320\270\321\201\320\260\320\273 \321\215\321\202\320\276 \320\277"
|
||||
"\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265.\n"
|
||||
"\n"
|
||||
"\320\234\321\213 \321\200\320\265\320\272\320\276\320\274\320\265\320\275"
|
||||
"\320\264\321\203\320\265\320\274 \320\262\320\260\320\274 \320\275\320\260"
|
||||
"\320\266\320\260\321\202\321\214 \"\320\236\321\202\320\274\320\265"
|
||||
"\320\275\320\260\" \320\270 \320\275\320\265 \320\267\320\260\320\277"
|
||||
"\321\203\321\201\320\272\320\260\321\202\321\214 \321\215\321\202\320\276 "
|
||||
"\320\277\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270"
|
||||
"\320\265.",
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
unknown_auth_cert_text[LI_COUNT] = {
|
||||
"This Panda3D application has been signed, but we don't recognize "
|
||||
"the authority that verifies the signature. This means the author's "
|
||||
"name can't be verified, and you have no way of knowing "
|
||||
"for sure who wrote it.\n"
|
||||
"\n"
|
||||
"We recommend you click Cancel to avoid running this application.",
|
||||
|
||||
// nld
|
||||
"Deze Panda3D applicatie is ondertekend, maar de certificaatautoriteit "
|
||||
"die het certificaat heeft uitgegeven wordt niet herkend. Dit betekent "
|
||||
"dat de auteursnaam niet te vertrouwen is, en het niet zeker is wie de "
|
||||
"applicatie gemaakt heeft.\n"
|
||||
"\n"
|
||||
"Het is aanbevolen om op Annuleren te klikken om de applicatie af te "
|
||||
"sluiten.",
|
||||
|
||||
// deu
|
||||
"Diese Panda3D-Anwendung wurde signiert, aber die "
|
||||
"\303\234berpr\303\274fungsstelle wurde nicht anerkannt. Dies bedeutet, "
|
||||
"dass weder der Name des Autors \303\274berpr\303\274ft werden kann, noch "
|
||||
"dass garantiert werden kann, dass tats\303\244chlich die angegebene "
|
||||
"Person diese Anwendung geschrieben hat.\n"
|
||||
"\n"
|
||||
"Wir empfehlen, dass Sie Abbrechen dr\303\274cken um diese Anwendung nicht "
|
||||
"auszuf\303\274hren.",
|
||||
|
||||
// spa
|
||||
"Esta aplicaci\303\263n de Panda3D esta firmada, pero no reconocemos la "
|
||||
"autoridad que la verifica. Esto significa que el nombre del autor no "
|
||||
"puede ser verificado y no se puede conocer seguro quien la ha escrito.\n"
|
||||
"\n"
|
||||
"Se recomienda cancelar para evitar ejecutar esta aplicaci\303\263n.",
|
||||
|
||||
// ita
|
||||
"Questa applicazione Panda3D \303\250 stata firmata, ma non riconosciamo "
|
||||
"l'autorit\303\240 che verifica la firma. Ci\303\262 significa che il "
|
||||
"nome dell'autore non pu\303\262 essere verificato, e che non hai modo "
|
||||
"di assicurarti circa chi la abbia scritta.\n"
|
||||
"\n"
|
||||
"Raccomandiamo di cliccare su Cancella per evitare di lanciare questa "
|
||||
"applicazione.",
|
||||
|
||||
// epo
|
||||
"\304\210i tiu aplika\304\265o estas subskribita, sed ni ne rekonas "
|
||||
"la a\305\255toritaton, kiu kontrolas la subskribon. Tio signifas ke la "
|
||||
"nomo de la verkanto ne povas esti konfidata, kaj vi ne havas certan "
|
||||
"scimanieron pri la vera verkanto de la aplika\304\265o.\n"
|
||||
"\n"
|
||||
"Ni rekomendas ke vi premas la butonon 'Nuligi' por eviti lan\304\211on "
|
||||
"de \304\211i tiu aplika\304\265o.",
|
||||
|
||||
// rus
|
||||
"\320\243 \321\215\321\202\320\276\320\263\320\276 \320\277\321\200\320\270"
|
||||
"\320\273\320\276\320\266\320\265\320\275\320\270\321\217 \320\265\321\201"
|
||||
"\321\202\321\214 \320\277\320\276\320\264\320\277\320\270\321\201\321\214,"
|
||||
" \320\277\320\276 \320\272\320\276\321\202\320\276\321\200\320\276\320\271"
|
||||
" \320\274\321\213 \320\275\320\265 \320\274\320\276\320\266\320\265"
|
||||
"\320\274 \321\200\320\260\321\201\320\277\320\276\320\267\320\275\320\260"
|
||||
"\321\202\321\214 \320\262\320\273\320\260\320\264\320\265\320\273\321\214"
|
||||
"\321\206\320\260. \320\255\321\202\320\276 \320\276\320\267\320\275"
|
||||
"\320\260\321\207\320\260\320\265\321\202, \321\207\321\202\320\276 "
|
||||
"\320\270\320\274\321\217 \320\260\320\262\321\202\320\276\321\200\320\260 "
|
||||
"\320\275\320\265 \320\274\320\276\320\266\320\265\321\202 \320\261\321\213"
|
||||
"\321\202\321\214 \320\276\320\277\321\200\320\265\320\264\320\265\320\273"
|
||||
"\320\265\320\275\320\276, \320\270 \320\275\320\265\320\262\320\276"
|
||||
"\320\267\320\274\320\276\320\266\320\275\320\276 \321\202\320\276\321\207"
|
||||
"\320\275\320\276 \321\203\320\267\320\275\320\260\321\202\321\214, "
|
||||
"\320\272\321\202\320\276 \320\275\320\260\320\277\320\270\321\201\320\260"
|
||||
"\320\273 \321\215\321\202\320\276 \320\277\321\200\320\270\320\273\320\276"
|
||||
"\320\266\320\265\320\275\320\270\320\265.\n"
|
||||
"\n"
|
||||
"\320\234\321\213 \321\200\320\265\320\272\320\276\320\274\320\265\320\275"
|
||||
"\320\264\321\203\320\265\320\274 \320\262\320\260\320\274 \320\275\320\260"
|
||||
"\320\266\320\260\321\202\321\214 \"\320\236\321\202\320\274\320\265"
|
||||
"\320\275\320\260\" \320\270 \320\275\320\265 \320\267\320\260\320\277"
|
||||
"\321\203\321\201\320\272\320\260\321\202\321\214 \321\215\321\202\320\276 "
|
||||
"\320\277\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270"
|
||||
"\320\265.",
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
verified_cert_text[LI_COUNT] = {
|
||||
// eng
|
||||
"This Panda3D application has been signed by %s. "
|
||||
"If you trust %s, then click the Run button below "
|
||||
"to run this application on your computer. This will also "
|
||||
"automatically approve this and any other applications signed by "
|
||||
"%s in the future.\n"
|
||||
"\n"
|
||||
"If you are unsure about this application, "
|
||||
"you should click Cancel instead.",
|
||||
|
||||
// nld
|
||||
"Deze Panda3D applicatie is ondertekend door %s. "
|
||||
"Als u %s vertrouwt, klik dan de onderstaande knop Uitvoeren om de applicatie "
|
||||
"op uw computer uit te voeren. Dit zal ook deze en andere door %s getekende "
|
||||
"applicaties in het vervolg toestaan.\n"
|
||||
"\n"
|
||||
"Als u niet zeker bent over deze applicatie is het aanbevolen om op "
|
||||
"Annuleren te klikken.",
|
||||
|
||||
// deu
|
||||
"Diese Panda3D-Anwendung wurde von %s signiert. Falls Sie %s vertrauen, "
|
||||
"dr\303\274cken Sie den Starten-Knopf, um diese Anwendung auszuf\303\274hren. "
|
||||
"Zudem werden in der Zukunft diese und alle anderen von %s signierten "
|
||||
"Anwendungen automatisch als vertrauensw\303\274rdig anerkannt.\n"
|
||||
"\n"
|
||||
"Falls Sie sich unsicher \303\274ber diese Anwendung sind, sollten Sie "
|
||||
"Abbrechen dr\303\274cken.",
|
||||
|
||||
// spa
|
||||
"Esta aplicaci\303\263n de Panda3D ha sido firmada por %s. Si se considera %s"
|
||||
"de confianza el bot\303\263n inferior ejecutar\303\241 la aplicaci\303\263n."
|
||||
"Esto validara esta y cualquier otra applizacion firmada por %s en el futuro.\n"
|
||||
"\n"
|
||||
"Si se duda de la aplicaci\303\263nse recomienda cancelar."
|
||||
|
||||
// ita
|
||||
"Questa applicazione Panda3D \303\250 stata firmata da %s. Se %s \303\250 "
|
||||
"un'entit\303\240 fidata, allora clicca il bottone Lancia sottostante per "
|
||||
"lanciare questa applicazione sul tuo computer. Inoltre, ci\303\262 "
|
||||
"approver\303\240 automaticamente questa e ogni altra applicazione "
|
||||
"firmata da %s in futuro.\n"
|
||||
"\n"
|
||||
"Se non sei sicuro circa questa applicazione, dovresti invece cliccare "
|
||||
"su Cancella.",
|
||||
|
||||
// epo
|
||||
"\304\210i tiu aplika\304\265o estas subskribita de %s. "
|
||||
"Se %s estas fidinda la\305\255 vi, premu la butonon 'Lan\304\211i' sube por "
|
||||
"lan\304\211i \304\211i tiun aplika\304\265on per via komputilo. Anka\305\255 "
|
||||
"tio a\305\255tomate estonece aprobos \304\211i tiun kaj alian ajn "
|
||||
"aplika\304\265on, kiu estas subskribita de %s.\n"
|
||||
"\n"
|
||||
"Se vi ne estas certa pri \304\211i tiu aplika\304\265o, "
|
||||
"vi anstata\305\255e povus premi la butonon 'Nuligi'.",
|
||||
|
||||
// rus
|
||||
"\320\255\321\202\320\276 \320\277\321\200\320\270\320\273\320\276\320\266"
|
||||
"\320\265\320\275\320\270\320\265 \320\277\320\276\320\264\320\277\320\270"
|
||||
"\321\201\320\260\320\275\320\276 %s. "
|
||||
"\320\225\321\201\320\273\320\270 \320\262\321\213 \320\264\320\276\320\262"
|
||||
"\320\265\321\200\321\217\320\265\321\202\320\265 %s, \320\275\320\260"
|
||||
"\320\266\320\274\320\270\321\202\320\265 \320\272\320\275\320\276\320\277"
|
||||
"\320\272\321\203 \"\320\227\320\260\320\277\321\203\321\201\321\202"
|
||||
"\320\270\321\202\321\214\" \320\262\320\275\320\270\320\267\321\203, "
|
||||
"\321\207\321\202\320\276\320\261\321\213 \320\267\320\260\320\277\321\203"
|
||||
"\321\201\321\202\320\270\321\202\321\214 \321\215\321\202\320\276 \320\277"
|
||||
"\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\320\265 "
|
||||
"\320\275\320\260 \320\262\320\260\321\210\320\265\320\274 \320\272\320\276"
|
||||
"\320\274\320\277\321\214\321\216\321\202\320\265\321\200\320\265. \320\255"
|
||||
"\321\202\320\276 \321\202\320\260\320\272\320\266\320\265 \320\260\320\262"
|
||||
"\321\202\320\276\320\274\320\260\321\202\320\270\321\207\320\265\321\201"
|
||||
"\320\272\320\270 \320\277\320\276\320\264\321\202\320\262\320\265\321\200"
|
||||
"\320\264\320\270\321\202 \321\215\321\202\320\276 \320\270 \320\264"
|
||||
"\321\200\321\203\320\263\320\270\320\265 \320\277\321\200\320\270\320\273"
|
||||
"\320\276\320\266\320\265\320\275\320\270\321\217, \320\275\320\260\320\277"
|
||||
"\320\270\321\201\320\260\320\275\320\275\321\213\320\265 %s \320\262 "
|
||||
"\320\261\321\203\320\264\321\203\321\211\320\265\320\274.\n"
|
||||
"\n"
|
||||
"\320\225\321\201\320\273\320\270 \320\262\321\213 \320\275\320\265 "
|
||||
"\321\203\320\262\320\265\321\200\320\265\320\275\321\213 \320\262 \321\215"
|
||||
"\321\202\320\276\320\274 \320\277\321\200\320\270\320\273\320\276\320\266"
|
||||
"\320\265\320\275\320\270\320\270, \320\275\320\260\320\266\320\274\320\270"
|
||||
"\321\202\320\265 \320\272\320\275\320\276\320\277\320\272\321\203 \""
|
||||
"\320\236\321\202\320\274\320\265\320\275\320\260\".",
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
expired_cert_text[LI_COUNT] = {
|
||||
// eng
|
||||
"This Panda3D application has been signed by %s, "
|
||||
"but the certificate has expired.\n"
|
||||
"\n"
|
||||
"You should check the current date set on your computer's clock "
|
||||
"to make sure it is correct.\n"
|
||||
"\n"
|
||||
"If your computer's date is correct, we recommend "
|
||||
"you click Cancel to avoid running this application.",
|
||||
|
||||
// nld
|
||||
"Deze Panda3D applicatie is ondertekend door %s, maar de geldigheidsdatum "
|
||||
"van het certificaat is verstreken.\n"
|
||||
"\n"
|
||||
"Controleer de datum op uw computerklok om te zorgen dat deze juist is "
|
||||
"ingesteld.\n"
|
||||
"\n"
|
||||
"Als de datum op uw computer juist is, is het aanbevolen om op Annuleren te "
|
||||
"klikken om de applicatie af te sluiten.",
|
||||
|
||||
// deu
|
||||
"Diese Anwendung wurde von %s signiert, aber das Zertifikat ist abgelaufen.\n"
|
||||
"\n"
|
||||
"Sie sollten die aktuelle Uhrzeit auf Ihrem Computer "
|
||||
"\303\274berpr\303\274fen, um sicherzugehen, dass sie korrekt ist.\n"
|
||||
"\n"
|
||||
"Falls die Uhrzeit auf Ihrem Computer korrekt ist, empfehlen wir Ihnen "
|
||||
"Abbrechen zu dr\303\274cken.",
|
||||
|
||||
// spa
|
||||
"Esta aplicaci\303\263n Panda3D ha sido firmada por %s pero el certificado ha"
|
||||
"caducado.\n"
|
||||
"\n"
|
||||
"Se recomienda comprobar la fecha del reloj.\n"
|
||||
"\n"
|
||||
"Si la fecha del reloj es correcta se recomienda cancelar.",
|
||||
|
||||
// ita
|
||||
"Questa applicazione Panda3D \303\250 stata firmata da %s, ma il "
|
||||
"certificato \303\250 scaduto.\n"
|
||||
"\n"
|
||||
"Dovresti controllare la data attuale impostata nell'orologio del tuo "
|
||||
"computer per assicurarti che sia corretta.\n"
|
||||
"\n"
|
||||
"Se la data del tuo computer \303\250 corretta, raccomandiamo di cliccare "
|
||||
"Cancella per evitare di lanciare questa applicazione."
|
||||
|
||||
// epo
|
||||
"\304\210i tiu aplika\304\265o de Panda3D estas subskribita de %s, "
|
||||
"sed la atesto eksvalidi\304\235is.\n"
|
||||
"\n"
|
||||
"Vi povus kontroli la aktualan daton, kiu estas agordata sur la horlo\304\235o de "
|
||||
"via komputilo por certigi ke \304\235i estas korekta.\n"
|
||||
"\n"
|
||||
"Se la dato de via komputilo estas korekta, ni rekomendas ke vi premas la "
|
||||
"butonon 'Nuligi' por eviti lan\304\211on de \304\211i tiu aplika\304\265o.",
|
||||
|
||||
// rus
|
||||
"\320\255\321\202\320\276 \320\277\321\200\320\270\320\273\320\276\320\266"
|
||||
"\320\265\320\275\320\270\320\265 \320\277\320\276\320\264\320\277\320\270"
|
||||
"\321\201\320\260\320\275\320\276 %s, \320\276\320\264\320\275\320\260"
|
||||
"\320\272\320\276 \321\201\321\200\320\276\320\272 \320\264\320\265\320\271"
|
||||
"\321\201\321\202\320\262\320\270\321\217 \321\201\320\265\321\200\321\202"
|
||||
"\320\270\321\204\320\270\320\272\320\260\321\202\320\260 \320\270\321\201"
|
||||
"\321\202\321\221\320\272.\n"
|
||||
"\n"
|
||||
"\320\237\321\200\320\276\320\262\320\265\321\200\321\214\321\202\320\265, "
|
||||
"\320\277\320\276\320\266\320\260\320\273\321\203\320\271\321\201\321\202"
|
||||
"\320\260, \320\264\320\260\321\202\321\203 \320\275\320\260 \320\262"
|
||||
"\320\260\321\210\320\265\320\274 \320\272\320\276\320\274\320\277\321\214"
|
||||
"\321\216\321\202\320\265\321\200\320\265.\n"
|
||||
"\n"
|
||||
"\320\225\321\201\320\273\320\270 \320\275\320\260 \320\262\320\260\321\210"
|
||||
"\320\265\320\274 \320\272\320\276\320\274\320\277\321\214\321\216\321\202"
|
||||
"\320\265\321\200\320\265 \321\203\321\201\321\202\320\260\320\275\320\276"
|
||||
"\320\262\320\273\320\265\320\275\320\276 \320\277\321\200\320\260\320\262"
|
||||
"\320\270\320\273\321\214\320\275\320\276\320\265 \320\262\321\200\320\265"
|
||||
"\320\274\321\217, \320\274\321\213 \321\200\320\265\320\272\320\276"
|
||||
"\320\274\320\265\320\275\320\264\321\203\320\265\320\274 \320\262\320\260"
|
||||
"\320\274 \320\275\320\260\320\266\320\260\321\202\321\214 \320\272\320\275"
|
||||
"\320\276\320\277\320\272\321\203 \"\320\236\321\202\320\274\320\265"
|
||||
"\320\275\320\260\" \320\270 \320\275\320\265 \320\267\320\260\320\277"
|
||||
"\321\203\321\201\320\272\320\260\321\202\321\214 \321\215\321\202\320\276 "
|
||||
"\320\277\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270"
|
||||
"\320\265.",
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
generic_error_cert_text[LI_COUNT] = {
|
||||
// eng
|
||||
"This Panda3D application has been signed, but there is a problem "
|
||||
"with the certificate (OpenSSL error code %d).\n"
|
||||
"\n"
|
||||
"We recommend you click Cancel to avoid running this application.",
|
||||
|
||||
// nld
|
||||
"Deze Panda3D applicatie is ondertekend, maar er is een probleem "
|
||||
"met het certificaat opgetreden (OpenSSL foutcode %d).\n"
|
||||
"\n"
|
||||
"Het is aanbevolen om op Annuleren te klikken om de applicatie af te "
|
||||
"sluiten.",
|
||||
|
||||
// deu
|
||||
"Diese Panda3D-Anwendung wurde signiert, aber es gibt ein Problem mit "
|
||||
"dem Zertifikat (OpenSSL Fehlercode %d).\n"
|
||||
"\n"
|
||||
"Wir empfehlen, dass Sie Abbrechen dr\303\274cken um diese Anwendung nicht "
|
||||
"auszuf\303\274hren.",
|
||||
|
||||
// spa
|
||||
"Esta aplicaci\303\263n de Panda3D esta firmada pero hay un problema con el "
|
||||
"certificado (Error de OpenSSL %d).\n"
|
||||
"\n"
|
||||
"Se recomienda cancelar para evitar ejecutar esta aplicaci\303\263n.",
|
||||
|
||||
// ita
|
||||
"Questa applicazione Panda3D \303\250 stata firmata, ma c'\303\250 un "
|
||||
"problema col certificato (codice di errore OpenSSL %s).\n"
|
||||
"\n"
|
||||
"Raccomandiamo di cliccare su Cancella per evitare di lanciare questa "
|
||||
"applicazione.",
|
||||
|
||||
// epo
|
||||
"\304\210i tiu aplika\304\265o de Panda3D estas subskribita, sed la "
|
||||
"atesto havas problemon (OpenSSL erarkodo %d).\n"
|
||||
"\n"
|
||||
"Ni rekomendas ke vi premas la butonon 'Nuligi' por eviti lan\304\211on "
|
||||
"de \304\211i tiu aplika\304\265o.",
|
||||
|
||||
// rus
|
||||
"\320\243 \321\215\321\202\320\276\320\263\320\276 \320\277\321\200\320\270"
|
||||
"\320\273\320\276\320\266\320\265\320\275\320\270\321\217 \320\265\321\201"
|
||||
"\321\202\321\214 \320\277\320\276\320\264\320\277\320\270\321\201\321\214,"
|
||||
" \320\275\320\276 \320\262\320\276\320\267\320\275\320\270\320\272\320\273"
|
||||
"\320\260 \320\277\321\200\320\276\320\261\320\273\320\265\320\274\320\260 "
|
||||
"\321\201 \321\201\320\265\321\200\321\202\320\270\321\204\320\270\320\272"
|
||||
"\320\260\321\202\320\276\320\274 (\320\232\320\276\320\264 \320\276"
|
||||
"\321\210\320\270\320\261\320\272\320\270 OpenSSL %d).\n"
|
||||
"\n"
|
||||
"\320\234\321\213 \321\200\320\265\320\272\320\276\320\274\320\265\320\275"
|
||||
"\320\264\321\203\320\265\320\274 \320\262\320\260\320\274 \320\275\320\260"
|
||||
"\320\266\320\260\321\202\321\214 \"\320\236\321\202\320\274\320\265"
|
||||
"\320\275\320\260\" \320\270 \320\275\320\265 \320\267\320\260\320\277"
|
||||
"\321\203\321\201\320\272\320\260\321\202\321\214 \321\215\321\202\320\276 "
|
||||
"\320\277\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270"
|
||||
"\320\265.",
|
||||
};
|
||||
|
||||
|
||||
const char *
|
||||
no_cert_text[LI_COUNT] = {
|
||||
"This Panda3D application has not been signed. This means you have "
|
||||
"no way of knowing for sure who wrote it.\n"
|
||||
"\n"
|
||||
"Click Cancel to avoid running this application.",
|
||||
|
||||
// nld
|
||||
"Deze Panda3D applicatie is niet ondertekend. Dit betekent dat het niet "
|
||||
"mogelijk is om de auteur te verifi\303\253ren.\n"
|
||||
"\n"
|
||||
"Klik op Annuleren om de applicatie af te sluiten.",
|
||||
|
||||
// deu
|
||||
"Diese Panda3D-Anwendung wurde nicht signiert. Es gibt keine "
|
||||
"M\303\266glichkeit festzustellen, wer diese entwickelt hat.\n"
|
||||
"\n"
|
||||
"Dr\303\274cken Sie Abbrechen, um diese Anwendung nicht auszuf\303\274hren.",
|
||||
|
||||
// spa
|
||||
"Esta aplicaci\303\263n de Panda3D no esta firmada, no hay forma de conocer "
|
||||
"quien la ha escrito.\n"
|
||||
"\n"
|
||||
"Cancelar para evitar ejecutar la aplicaci\303\263n.",
|
||||
|
||||
// ita
|
||||
"Questa applicazione Panda3D non \303\250 stata firmata. Ci\303\262 "
|
||||
"significa che non hai modo di assicurarti circa chi la abbia scritta.\n"
|
||||
"\n"
|
||||
"Clicca Cancella per evitare di lanciare questa applicazione.",
|
||||
|
||||
// epo
|
||||
"\304\210i tiu aplika\304\265o de Panda3D ne estas subskribita. Tio "
|
||||
"signifas ke vi ne havas certan scimanieron pri la vera verkanto de "
|
||||
"la aplika\304\265o.\n"
|
||||
"\n"
|
||||
"Premu la butonon 'Nuligi' por eviti lan\304\211on de \304\211i tiu "
|
||||
"aplika\304\265o.",
|
||||
|
||||
// rus
|
||||
"\320\243 \321\215\321\202\320\276\320\263\320\276 \320\277\321\200\320\270"
|
||||
"\320\273\320\276\320\266\320\265\320\275\320\270\321\217 \320\275\320\265"
|
||||
"\321\202 \320\277\320\276\320\264\320\277\320\270\321\201\320\270. "
|
||||
"\320\255\321\202\320\276 \320\276\320\267\320\275\320\260\321\207\320\260"
|
||||
"\320\265\321\202, \321\207\321\202\320\276 \320\262\321\213 \320\275"
|
||||
"\320\265 \320\274\320\276\320\266\320\265\321\202\320\265 \320\267\320\275"
|
||||
"\320\260\321\202\321\214, \320\272\321\202\320\276 \320\265\320\263"
|
||||
"\320\276 \320\275\320\260\320\277\320\270\321\201\320\260\320\273.\n"
|
||||
"\n"
|
||||
"\320\235\320\260\320\266\320\274\320\270\321\202\320\265 \"\320\236"
|
||||
"\321\202\320\274\320\265\320\275\320\260\", \321\207\321\202\320\276"
|
||||
"\320\261\321\213 \320\275\320\265 \320\267\320\260\320\277\321\203\321\201"
|
||||
"\320\272\320\260\321\202\321\214 \320\277\321\200\320\270\320\273\320\276"
|
||||
"\320\266\320\265\320\275\320\270\320\265.",
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCert_strings.h
|
||||
* @author rdb
|
||||
* @date 2015-03-25
|
||||
*/
|
||||
|
||||
enum LanguageIndex {
|
||||
LI_en, // English
|
||||
LI_nl, // Dutch
|
||||
LI_de, // German
|
||||
LI_es, // Spanish
|
||||
LI_it, // Italian
|
||||
LI_eo, // Esperanto
|
||||
LI_ru, // Russian
|
||||
LI_COUNT,
|
||||
|
||||
LI_default = LI_en
|
||||
};
|
||||
|
||||
extern const char *language_codes[LI_COUNT];
|
||||
extern const unsigned char language_ids[LI_COUNT];
|
||||
|
||||
extern const char *run_title[LI_COUNT];
|
||||
extern const char *cancel_title[LI_COUNT];
|
||||
extern const char *show_cert_title[LI_COUNT];
|
||||
|
||||
extern const char *new_application_title[LI_COUNT];
|
||||
extern const char *no_cert_title[LI_COUNT];
|
||||
extern const char *unverified_cert_title[LI_COUNT];
|
||||
extern const char *expired_cert_title[LI_COUNT];
|
||||
|
||||
extern const char *self_signed_cert_text[LI_COUNT];
|
||||
extern const char *unknown_auth_cert_text[LI_COUNT];
|
||||
extern const char *verified_cert_text[LI_COUNT];
|
||||
extern const char *expired_cert_text[LI_COUNT];
|
||||
extern const char *generic_error_cert_text[LI_COUNT];
|
||||
extern const char *no_cert_text[LI_COUNT];
|
||||
@@ -1,618 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCert_wx.cxx
|
||||
* @author drose
|
||||
* @date 2009-09-11
|
||||
*/
|
||||
|
||||
#include "p3dCert_wx.h"
|
||||
#include "wstring_encode.h"
|
||||
#include "mkdir_complete.h"
|
||||
|
||||
#include <wx/cmdline.h>
|
||||
#include <wx/filename.h>
|
||||
|
||||
#include "ca_bundle_data_src.c"
|
||||
|
||||
using std::cerr;
|
||||
|
||||
static const wxString
|
||||
self_signed_cert_text =
|
||||
_T("This Panda3D application uses a self-signed certificate. ")
|
||||
_T("This means the author's name can't be verified, and you have ")
|
||||
_T("no way of knowing for sure who wrote it.\n\n")
|
||||
|
||||
_T("We recommend you click Cancel to avoid running this application.");
|
||||
|
||||
static const wxString
|
||||
unknown_auth_cert_text =
|
||||
_T("This Panda3D application has been signed, but we don't recognize ")
|
||||
_T("the authority that verifies the signature. This means the author's ")
|
||||
_T("name can't be trusted, and you have no way of knowing ")
|
||||
_T("for sure who wrote it.\n\n")
|
||||
|
||||
_T("We recommend you click Cancel to avoid running this application.");
|
||||
|
||||
static const wxString
|
||||
verified_cert_text =
|
||||
_T("This Panda3D application has been signed by %s. ")
|
||||
_T("If you trust %s, then click the Run button below ")
|
||||
_T("to run this application on your computer. This will also ")
|
||||
_T("automatically approve this and any other applications signed by ")
|
||||
_T("%s in the future.\n\n")
|
||||
|
||||
_T("If you are unsure about this application, ")
|
||||
_T("you should click Cancel instead.");
|
||||
|
||||
static const wxString
|
||||
expired_cert_text =
|
||||
_T("This Panda3D application has been signed by %s, ")
|
||||
_T("but the certificate has expired.\n\n")
|
||||
|
||||
_T("You should check the current date set on your computer's clock ")
|
||||
_T("to make sure it is correct.\n\n")
|
||||
|
||||
_T("If your computer's date is correct, we recommend ")
|
||||
_T("you click Cancel to avoid running this application.");
|
||||
|
||||
static const wxString
|
||||
generic_error_cert_text =
|
||||
_T("This Panda3D application has been signed, but there is a problem ")
|
||||
_T("with the certificate (OpenSSL error code %d).\n\n")
|
||||
|
||||
_T("We recommend you click Cancel to avoid running this application.");
|
||||
|
||||
static const wxString
|
||||
no_cert_text =
|
||||
_T("This Panda3D application has not been signed. This means you have ")
|
||||
_T("no way of knowing for sure who wrote it.\n\n")
|
||||
|
||||
_T("Click Cancel to avoid running this application.");
|
||||
|
||||
// wxWidgets boilerplate macro to define main() and start up the application.
|
||||
IMPLEMENT_APP(P3DCertApp)
|
||||
|
||||
/**
|
||||
* The "main" of a wx application. This is the first entry point.
|
||||
*/
|
||||
bool P3DCertApp::
|
||||
OnInit() {
|
||||
// call the base class initialization method, currently it only parses a few
|
||||
// common command-line options but it could be do more in the future
|
||||
if (!wxApp::OnInit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
AuthDialog *dialog = new AuthDialog(_cert_filename, _cert_dir);
|
||||
SetTopWindow(dialog);
|
||||
dialog->Show(true);
|
||||
dialog->SetFocus();
|
||||
dialog->Raise();
|
||||
|
||||
// Return true to enter the main loop and wait for user input.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback to initialize the parser with the command line options.
|
||||
*/
|
||||
void P3DCertApp::
|
||||
OnInitCmdLine(wxCmdLineParser &parser) {
|
||||
parser.AddParam();
|
||||
parser.AddParam();
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback after the successful parsing of the command line.
|
||||
*/
|
||||
bool P3DCertApp::
|
||||
OnCmdLineParsed(wxCmdLineParser &parser) {
|
||||
_cert_filename = (const char *)parser.GetParam(0).mb_str();
|
||||
_cert_dir = (const char *)parser.GetParam(1).mb_str();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// The event table for AuthDialog.
|
||||
#define VIEW_CERT_BUTTON (wxID_HIGHEST + 1)
|
||||
BEGIN_EVENT_TABLE(AuthDialog, wxDialog)
|
||||
EVT_BUTTON(wxID_OK, AuthDialog::run_clicked)
|
||||
EVT_BUTTON(VIEW_CERT_BUTTON, AuthDialog::view_cert_clicked)
|
||||
EVT_BUTTON(wxID_CANCEL, AuthDialog::cancel_clicked)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
AuthDialog::
|
||||
AuthDialog(const std::string &cert_filename, const std::string &cert_dir) :
|
||||
// I hate stay-on-top dialogs, but if we don't set this flag, it doesn't
|
||||
// come to the foreground on OSX, and might be lost behind the browser
|
||||
// window.
|
||||
wxDialog(nullptr, wxID_ANY, _T("New Panda3D Application"), wxDefaultPosition,
|
||||
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxSTAY_ON_TOP),
|
||||
_cert_dir(cert_dir)
|
||||
{
|
||||
_view_cert_dialog = nullptr;
|
||||
|
||||
_cert = nullptr;
|
||||
_stack = nullptr;
|
||||
_verify_result = -1;
|
||||
|
||||
read_cert_file(cert_filename);
|
||||
get_friendly_name();
|
||||
verify_cert();
|
||||
layout();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
AuthDialog::
|
||||
~AuthDialog() {
|
||||
if (_view_cert_dialog != nullptr) {
|
||||
_view_cert_dialog->Destroy();
|
||||
}
|
||||
|
||||
if (_cert != nullptr) {
|
||||
X509_free(_cert);
|
||||
_cert = nullptr;
|
||||
}
|
||||
if (_stack != nullptr) {
|
||||
sk_X509_free(_stack);
|
||||
_stack = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Run" button.
|
||||
*/
|
||||
void AuthDialog::
|
||||
run_clicked(wxCommandEvent &event) {
|
||||
approve_cert();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "View Certificate" button.
|
||||
*/
|
||||
void AuthDialog::
|
||||
view_cert_clicked(wxCommandEvent &event) {
|
||||
if (_view_cert_dialog != nullptr) {
|
||||
_view_cert_dialog->Destroy();
|
||||
}
|
||||
Hide();
|
||||
_view_cert_dialog = new ViewCertDialog(this, _cert);
|
||||
_view_cert_dialog->Show();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Cancel" button.
|
||||
*/
|
||||
void AuthDialog::
|
||||
cancel_clicked(wxCommandEvent &event) {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the certificate into the _cert_dir, so that it will be found by the
|
||||
* P3DInstanceManager and known to be approved.
|
||||
*/
|
||||
void AuthDialog::
|
||||
approve_cert() {
|
||||
assert(_cert != nullptr);
|
||||
|
||||
// Make sure the directory exists.
|
||||
mkdir_complete(_cert_dir, cerr);
|
||||
|
||||
// Look for an unused filename.
|
||||
int i = 1;
|
||||
size_t buf_length = _cert_dir.length() + 100;
|
||||
char *buf = new char[buf_length];
|
||||
#ifdef _WIN32
|
||||
std::wstring buf_w;
|
||||
#endif // _WIN32
|
||||
|
||||
while (true) {
|
||||
sprintf(buf, "%s/p%d.crt", _cert_dir.c_str(), i);
|
||||
assert(strlen(buf) < buf_length);
|
||||
|
||||
// Check if it already exists. If not, take it.
|
||||
#ifdef _WIN32
|
||||
DWORD results = 0;
|
||||
if (string_to_wstring(buf_w, buf)) {
|
||||
results = GetFileAttributesW(buf_w.c_str());
|
||||
}
|
||||
if (results == -1) {
|
||||
break;
|
||||
}
|
||||
#else
|
||||
struct stat statbuf;
|
||||
if (stat(buf, &statbuf) != 0) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
++i;
|
||||
}
|
||||
|
||||
// Sure, there's a slight race condition right now: another process might
|
||||
// attempt to create the same filename. So what.
|
||||
FILE *fp = nullptr;
|
||||
#ifdef _WIN32
|
||||
fp = _wfopen(buf_w.c_str(), L"w");
|
||||
#else // _WIN32
|
||||
fp = fopen(buf, "w");
|
||||
#endif // _WIN32
|
||||
if (fp != nullptr) {
|
||||
PEM_write_X509(fp, _cert);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
Destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the list of certificates in the pem filename passed on the command
|
||||
* line into _cert and _stack.
|
||||
*/
|
||||
void AuthDialog::
|
||||
read_cert_file(const std::string &cert_filename) {
|
||||
FILE *fp = nullptr;
|
||||
#ifdef _WIN32
|
||||
std::wstring cert_filename_w;
|
||||
if (string_to_wstring(cert_filename_w, cert_filename)) {
|
||||
fp = _wfopen(cert_filename_w.c_str(), L"r");
|
||||
}
|
||||
#else // _WIN32
|
||||
fp = fopen(cert_filename.c_str(), "r");
|
||||
#endif // _WIN32
|
||||
|
||||
if (fp == nullptr) {
|
||||
cerr << "Couldn't read " << cert_filename << "\n";
|
||||
return;
|
||||
}
|
||||
_cert = PEM_read_X509(fp, nullptr, nullptr, (void *)"");
|
||||
if (_cert == nullptr) {
|
||||
cerr << "Could not read certificate in " << cert_filename << ".\n";
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build up a STACK of the remaining certificates in the file.
|
||||
_stack = sk_X509_new(nullptr);
|
||||
X509 *c = PEM_read_X509(fp, nullptr, nullptr, (void *)"");
|
||||
while (c != nullptr) {
|
||||
sk_X509_push(_stack, c);
|
||||
c = PEM_read_X509(fp, nullptr, nullptr, (void *)"");
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the "friendly name" from the certificate: the common name or email
|
||||
* name.
|
||||
*/
|
||||
void AuthDialog::
|
||||
get_friendly_name() {
|
||||
if (_cert == nullptr) {
|
||||
_friendly_name.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static const int nid_choices[] = {
|
||||
NID_pkcs9_emailAddress,
|
||||
NID_commonName,
|
||||
-1,
|
||||
};
|
||||
|
||||
// Choose the first NID that exists on the cert.
|
||||
for (int ni = 0; nid_choices[ni] != -1; ++ni) {
|
||||
int nid = nid_choices[ni];
|
||||
|
||||
// A complex OpenSSL interface to extract out the name in utf-8.
|
||||
X509_NAME *xname = X509_get_subject_name(_cert);
|
||||
if (xname != nullptr) {
|
||||
int pos = X509_NAME_get_index_by_NID(xname, nid, -1);
|
||||
if (pos != -1) {
|
||||
// We just get the first common name. I guess it's possible to have
|
||||
// more than one; not sure what that means in this context.
|
||||
X509_NAME_ENTRY *xentry = X509_NAME_get_entry(xname, pos);
|
||||
if (xentry != nullptr) {
|
||||
ASN1_STRING *data = X509_NAME_ENTRY_get_data(xentry);
|
||||
if (data != nullptr) {
|
||||
// We use "print" to dump the output to a memory BIO. Is there an
|
||||
// easier way to decode the ASN1_STRING? Curse these incomplete
|
||||
// docs.
|
||||
BIO *mbio = BIO_new(BIO_s_mem());
|
||||
ASN1_STRING_print_ex(mbio, data, ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB);
|
||||
|
||||
char *pp;
|
||||
long pp_size = BIO_get_mem_data(mbio, &pp);
|
||||
_friendly_name = wxString(pp, wxConvUTF8, pp_size);
|
||||
BIO_free(mbio);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the certificate is valid by the chain and initializes
|
||||
* _verify_status accordingly.
|
||||
*/
|
||||
void AuthDialog::
|
||||
verify_cert() {
|
||||
if (_cert == nullptr) {
|
||||
_verify_result = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new X509_STORE.
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
X509_STORE_set_default_paths(store);
|
||||
|
||||
// Add in the well-known certificate authorities.
|
||||
load_certificates_from_der_ram(store, (const char *)ca_bundle_data, ca_bundle_data_len);
|
||||
|
||||
// Create the X509_STORE_CTX for verifying the cert and chain.
|
||||
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
|
||||
X509_STORE_CTX_init(ctx, store, _cert, _stack);
|
||||
X509_STORE_CTX_set_cert(ctx, _cert);
|
||||
|
||||
if (X509_verify_cert(ctx)) {
|
||||
_verify_result = 0;
|
||||
} else {
|
||||
_verify_result = X509_STORE_CTX_get_error(ctx);
|
||||
}
|
||||
|
||||
X509_STORE_CTX_free(ctx);
|
||||
|
||||
X509_STORE_free(store);
|
||||
|
||||
cerr << "Got certificate from " << _friendly_name.mb_str()
|
||||
<< ", verify_result = " << _verify_result << "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a chain of trusted certificates from the indicated data buffer and
|
||||
* adds them to the X509_STORE object. The data buffer should be DER-
|
||||
* formatted. Returns the number of certificates read on success, or 0 on
|
||||
* failure.
|
||||
*
|
||||
* You should call this only with trusted, locally-stored certificates; not
|
||||
* with certificates received from an untrusted source.
|
||||
*/
|
||||
int AuthDialog::
|
||||
load_certificates_from_der_ram(X509_STORE *store,
|
||||
const char *data, size_t data_size) {
|
||||
int count = 0;
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
|
||||
// Beginning in 0.9.8, d2i_X509() accepted a const unsigned char **.
|
||||
const unsigned char *bp, *bp_end;
|
||||
#else
|
||||
// Prior to 0.9.8, d2i_X509() accepted an unsigned char **.
|
||||
unsigned char *bp, *bp_end;
|
||||
#endif
|
||||
|
||||
bp = (unsigned char *)data;
|
||||
bp_end = bp + data_size;
|
||||
X509 *x509 = d2i_X509(nullptr, &bp, bp_end - bp);
|
||||
while (x509 != nullptr) {
|
||||
X509_STORE_add_cert(store, x509);
|
||||
++count;
|
||||
x509 = d2i_X509(nullptr, &bp, bp_end - bp);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges the text and controls within the dialog.
|
||||
*/
|
||||
void AuthDialog::
|
||||
layout() {
|
||||
wxString header, text;
|
||||
get_text(header, text);
|
||||
|
||||
wxPanel *panel = new wxPanel(this);
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxFont font = panel->GetFont();
|
||||
wxFont *bold_font = wxTheFontList->FindOrCreateFont
|
||||
((int)(font.GetPointSize() * 1.5),
|
||||
font.GetFamily(), font.GetStyle(), wxFONTWEIGHT_BOLD);
|
||||
|
||||
if (!header.IsEmpty()) {
|
||||
wxStaticText *text0 = new wxStaticText
|
||||
(panel, wxID_ANY, header, wxDefaultPosition, wxDefaultSize,
|
||||
wxALIGN_CENTER);
|
||||
text0->SetFont(*bold_font);
|
||||
vsizer->Add(text0, 0, wxCENTER | wxALL, 10);
|
||||
}
|
||||
|
||||
wxStaticText *text1 = new wxStaticText
|
||||
(panel, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
|
||||
text1->Wrap(400);
|
||||
vsizer->Add(text1, 0, wxCENTER | wxALL, 10);
|
||||
|
||||
// Create the run cancel buttons.
|
||||
wxBoxSizer *bsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
if (_verify_result == 0 && _cert != nullptr) {
|
||||
wxButton *run_button = new wxButton(panel, wxID_OK, _T("Run"));
|
||||
bsizer->Add(run_button, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
}
|
||||
|
||||
if (_cert != nullptr) {
|
||||
wxButton *view_button = new wxButton(panel, VIEW_CERT_BUTTON, _T("View Certificate"));
|
||||
bsizer->Add(view_button, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
}
|
||||
|
||||
wxButton *cancel_button = new wxButton(panel, wxID_CANCEL, _T("Cancel"));
|
||||
bsizer->Add(cancel_button, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
vsizer->Add(bsizer, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
panel->SetSizer(vsizer);
|
||||
panel->SetAutoLayout(true);
|
||||
vsizer->Fit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the text appropriate to display in the dialog box, based on the
|
||||
* certificate read so far.
|
||||
*/
|
||||
void AuthDialog::
|
||||
get_text(wxString &header, wxString &text) {
|
||||
switch (_verify_result) {
|
||||
case -1:
|
||||
header = _T("No signature!");
|
||||
text = no_cert_text;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
text.Printf(verified_cert_text, _friendly_name.c_str(), _friendly_name.c_str(), _friendly_name.c_str());
|
||||
break;
|
||||
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
case X509_V_ERR_CRL_NOT_YET_VALID:
|
||||
case X509_V_ERR_CRL_HAS_EXPIRED:
|
||||
header = _T("Expired signature!");
|
||||
text.Printf(expired_cert_text, _friendly_name.c_str());
|
||||
break;
|
||||
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
header = _T("Unverified signature!");
|
||||
text.Printf(unknown_auth_cert_text, _friendly_name.c_str());
|
||||
break;
|
||||
|
||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
header = _T("Unverified signature!");
|
||||
text = self_signed_cert_text;
|
||||
break;
|
||||
|
||||
default:
|
||||
header = _T("Unverified signature!");
|
||||
text.Printf(generic_error_cert_text, _verify_result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The event table for ViewCertDialog.
|
||||
BEGIN_EVENT_TABLE(ViewCertDialog, wxDialog)
|
||||
EVT_BUTTON(wxID_OK, ViewCertDialog::run_clicked)
|
||||
EVT_BUTTON(wxID_CANCEL, ViewCertDialog::cancel_clicked)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ViewCertDialog::
|
||||
ViewCertDialog(AuthDialog *auth_dialog, X509 *cert) :
|
||||
wxDialog(nullptr, wxID_ANY, _T("View Certificate"), wxDefaultPosition,
|
||||
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
_auth_dialog(auth_dialog),
|
||||
_cert(cert)
|
||||
{
|
||||
layout();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ViewCertDialog::
|
||||
~ViewCertDialog() {
|
||||
if (_auth_dialog != nullptr) {
|
||||
_auth_dialog->_view_cert_dialog = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Run" button.
|
||||
*/
|
||||
void ViewCertDialog::
|
||||
run_clicked(wxCommandEvent &event) {
|
||||
if (_auth_dialog != nullptr){
|
||||
_auth_dialog->approve_cert();
|
||||
}
|
||||
Destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user clicks the "Cancel" button.
|
||||
*/
|
||||
void ViewCertDialog::
|
||||
cancel_clicked(wxCommandEvent &event) {
|
||||
if (_auth_dialog != nullptr){
|
||||
_auth_dialog->Destroy();
|
||||
}
|
||||
Destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges the text and controls within the dialog.
|
||||
*/
|
||||
void ViewCertDialog::
|
||||
layout() {
|
||||
// Format the certificate text for display in the dialog.
|
||||
assert(_cert != nullptr);
|
||||
|
||||
BIO *mbio = BIO_new(BIO_s_mem());
|
||||
X509_print(mbio, _cert);
|
||||
|
||||
char *pp;
|
||||
long pp_size = BIO_get_mem_data(mbio, &pp);
|
||||
wxString cert_body(pp, wxConvUTF8, pp_size);
|
||||
BIO_free(mbio);
|
||||
|
||||
wxPanel *panel = new wxPanel(this);
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxScrolledWindow *slwin = new wxScrolledWindow
|
||||
(panel, -1, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL | wxBORDER_SUNKEN);
|
||||
slwin->SetScrollRate(20, 20);
|
||||
|
||||
wxBoxSizer *slsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxStaticText *text1 = new wxStaticText
|
||||
(slwin, wxID_ANY, cert_body, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
slsizer->Add(text1, 0, wxEXPAND, 0);
|
||||
slwin->SetSizer(slsizer);
|
||||
|
||||
vsizer->Add(slwin, 1, wxEXPAND | wxALL, 10);
|
||||
|
||||
// Create the run cancel buttons.
|
||||
wxBoxSizer *bsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
wxButton *run_button = new wxButton(panel, wxID_OK, _T("Run"));
|
||||
bsizer->Add(run_button, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
wxButton *cancel_button = new wxButton(panel, wxID_CANCEL, _T("Cancel"));
|
||||
bsizer->Add(cancel_button, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
vsizer->Add(bsizer, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
panel->SetSizer(vsizer);
|
||||
panel->SetAutoLayout(true);
|
||||
vsizer->Fit(this);
|
||||
|
||||
// Make sure the resulting window is at least a certain size.
|
||||
int width, height;
|
||||
GetSize(&width, &height);
|
||||
SetSize(std::max(width, 600), std::max(height, 400));
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dCert_wx.h
|
||||
* @author drose
|
||||
* @date 2009-09-11
|
||||
*/
|
||||
|
||||
#ifndef P3DCERT_WX_H
|
||||
#define P3DCERT_WX_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#define OPENSSL_NO_KRB5
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
class ViewCertDialog;
|
||||
|
||||
#ifndef STACK_OF
|
||||
// At some point, presumably in 1.0.0, openssl went to the STACK_OF() macro
|
||||
// system to typedef the contents of a stack. Unfortunately, that new API
|
||||
// is different. We define some macros here here for backward compatiblity.
|
||||
#define STACK_OF(type) STACK
|
||||
#define sk_X509_push(stack, item) sk_push((stack), (char *)(item))
|
||||
#define sk_X509_free(stack) sk_free(stack)
|
||||
#define sk_X509_new(cmp) sk_new(cmp)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is the wxApp that drives this application.
|
||||
*/
|
||||
class P3DCertApp : public wxApp {
|
||||
public:
|
||||
virtual bool OnInit();
|
||||
virtual void OnInitCmdLine(wxCmdLineParser &parser);
|
||||
virtual bool OnCmdLineParsed(wxCmdLineParser &parser);
|
||||
|
||||
private:
|
||||
std::string _cert_filename;
|
||||
std::string _cert_dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the primary dialog of this application.
|
||||
*
|
||||
* This dialog is presented to the user when he/she clicks on the red
|
||||
* authorization button on the splash window. It tells the user the status of
|
||||
* the application's signature, and invites the user to approve the signature
|
||||
* or cancel.
|
||||
*/
|
||||
class AuthDialog : public wxDialog {
|
||||
public:
|
||||
AuthDialog(const std::string &cert_filename, const std::string &cert_dir);
|
||||
virtual ~AuthDialog();
|
||||
|
||||
void run_clicked(wxCommandEvent &event);
|
||||
void view_cert_clicked(wxCommandEvent &event);
|
||||
void cancel_clicked(wxCommandEvent &event);
|
||||
|
||||
void approve_cert();
|
||||
|
||||
private:
|
||||
void read_cert_file(const std::string &cert_filename);
|
||||
void get_friendly_name();
|
||||
void verify_cert();
|
||||
int load_certificates_from_der_ram(X509_STORE *store,
|
||||
const char *data, size_t data_size);
|
||||
|
||||
void layout();
|
||||
void get_text(wxString &header, wxString &text);
|
||||
|
||||
public:
|
||||
ViewCertDialog *_view_cert_dialog;
|
||||
|
||||
private:
|
||||
// any class wishing to process wxWidgets events must use this macro
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
std::string _cert_dir;
|
||||
X509 *_cert;
|
||||
STACK_OF(X509) *_stack;
|
||||
|
||||
wxString _friendly_name;
|
||||
int _verify_result;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the detailed view of the particular certificate.
|
||||
*/
|
||||
class ViewCertDialog : public wxDialog {
|
||||
public:
|
||||
ViewCertDialog(AuthDialog *auth_dialog, X509 *cert);
|
||||
virtual ~ViewCertDialog();
|
||||
|
||||
void run_clicked(wxCommandEvent &event);
|
||||
void cancel_clicked(wxCommandEvent &event);
|
||||
|
||||
private:
|
||||
void layout();
|
||||
|
||||
private:
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
AuthDialog *_auth_dialog;
|
||||
X509 *_cert;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,213 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConcreteSequence.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#include "p3dConcreteSequence.h"
|
||||
#include "p3dSession.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DConcreteSequence::
|
||||
P3DConcreteSequence() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DConcreteSequence::
|
||||
~P3DConcreteSequence() {
|
||||
Elements::iterator ei;
|
||||
for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
|
||||
P3D_OBJECT_DECREF(*ei);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is actually an instance of a P3DConcreteSequence,
|
||||
* false otherwise.
|
||||
*/
|
||||
bool P3DConcreteSequence::
|
||||
is_sequence_object() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fundamental type of this kind of object.
|
||||
*/
|
||||
P3D_object_type P3DConcreteSequence::
|
||||
get_type() {
|
||||
return P3D_OT_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a boolean, if possible.
|
||||
*/
|
||||
bool P3DConcreteSequence::
|
||||
get_bool() {
|
||||
return !_elements.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the indicated C++ string object with the value of this object coerced
|
||||
* to a string.
|
||||
*/
|
||||
void P3DConcreteSequence::
|
||||
make_string(std::string &value) {
|
||||
std::ostringstream strm;
|
||||
strm << "[";
|
||||
if (!_elements.empty()) {
|
||||
strm << *_elements[0];
|
||||
for (size_t i = 1; i < _elements.size(); ++i) {
|
||||
strm << ", " << *_elements[i];
|
||||
}
|
||||
}
|
||||
strm << "]";
|
||||
|
||||
value = strm.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the named property element in the object. The return value is a
|
||||
* new-reference P3D_object, or NULL on error.
|
||||
*/
|
||||
P3D_object *P3DConcreteSequence::
|
||||
get_property(const std::string &property) {
|
||||
// We only understand integer "property" names.
|
||||
char *endptr;
|
||||
int index = strtoul(property.c_str(), &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return get_element(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies (or deletes, if value is NULL) the named property element in the
|
||||
* object. Returns true on success, false on failure.
|
||||
*/
|
||||
bool P3DConcreteSequence::
|
||||
set_property(const std::string &property, P3D_object *value) {
|
||||
// We only understand integer "property" names.
|
||||
char *endptr;
|
||||
int index = strtoul(property.c_str(), &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return set_element(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this object has a valid XML representation for the indicated session
|
||||
* (that hasn't already been implemented by the generic code in P3DSession),
|
||||
* this method will apply it to the indicated "value" element and return true.
|
||||
* Otherwise, this method will leave the element unchanged and return false.
|
||||
*/
|
||||
bool P3DConcreteSequence::
|
||||
fill_xml(TiXmlElement *xvalue, P3DSession *session) {
|
||||
xvalue->SetAttribute("type", "concrete_sequence");
|
||||
Elements::const_iterator ei;
|
||||
for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
|
||||
xvalue->LinkEndChild(session->p3dobj_to_xml(*ei));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the array of objects represented by this object. Most
|
||||
* objects represent only themselves, but a P3DConcreteSequence represents its
|
||||
* list.
|
||||
*/
|
||||
P3D_object **P3DConcreteSequence::
|
||||
get_object_array() {
|
||||
if (_elements.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &_elements[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in the array returned by get_object_array().
|
||||
*/
|
||||
int P3DConcreteSequence::
|
||||
get_object_array_size() {
|
||||
return _elements.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items in the sequence.
|
||||
*/
|
||||
int P3DConcreteSequence::
|
||||
get_length() const {
|
||||
return _elements.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth item in the sequence. The return value is a new-reference
|
||||
* P3DObject object, or NULL on error.
|
||||
*/
|
||||
P3D_object *P3DConcreteSequence::
|
||||
get_element(int n) const {
|
||||
if (n >= 0 && n < (int)_elements.size()) {
|
||||
P3D_OBJECT_INCREF(_elements[n]);
|
||||
return _elements[n];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies (or deletes, if value is NULL) the nth item in the sequence.
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
bool P3DConcreteSequence::
|
||||
set_element(int n, P3D_object *value) {
|
||||
if (value == nullptr) {
|
||||
// Delete an element.
|
||||
if (n < 0 || n >= (int)_elements.size()) {
|
||||
// Invalid index.
|
||||
return false;
|
||||
}
|
||||
P3D_OBJECT_DECREF(_elements[n]);
|
||||
_elements.erase(_elements.begin() + n);
|
||||
return true;
|
||||
|
||||
} else if (n == _elements.size()) {
|
||||
// Append an element.
|
||||
append(value);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Replace an element.
|
||||
if (n < 0 || n >= (int)_elements.size()) {
|
||||
// Invalid index.
|
||||
return false;
|
||||
}
|
||||
|
||||
P3D_OBJECT_INCREF(value);
|
||||
P3D_OBJECT_DECREF(_elements[n]);
|
||||
_elements[n] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new element to the end of the list.
|
||||
*/
|
||||
void P3DConcreteSequence::
|
||||
append(P3D_object *value) {
|
||||
_elements.push_back(value);
|
||||
P3D_OBJECT_INCREF(value);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConcreteSequence.h
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#ifndef P3DCONCRETESEQUENCE_H
|
||||
#define P3DCONCRETESEQUENCE_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dObject.h"
|
||||
|
||||
/**
|
||||
* An object type that contains a sequence of objects, which is passed by
|
||||
* value between Python and JavaScript, so may be more optimal for small lists
|
||||
* that are accessed repeatedly.
|
||||
*
|
||||
* This is converted from a Python "tuple" object.
|
||||
*/
|
||||
class P3DConcreteSequence : public P3DObject {
|
||||
public:
|
||||
P3DConcreteSequence();
|
||||
virtual ~P3DConcreteSequence();
|
||||
|
||||
virtual bool is_sequence_object();
|
||||
|
||||
virtual P3D_object_type get_type();
|
||||
virtual bool get_bool();
|
||||
|
||||
virtual void make_string(std::string &value);
|
||||
|
||||
virtual P3D_object *get_property(const std::string &property);
|
||||
virtual bool set_property(const std::string &property, P3D_object *value);
|
||||
|
||||
virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
|
||||
virtual P3D_object **get_object_array();
|
||||
virtual int get_object_array_size();
|
||||
|
||||
int get_length() const;
|
||||
P3D_object *get_element(int n) const;
|
||||
bool set_element(int n, P3D_object *value);
|
||||
void append(P3D_object *value);
|
||||
|
||||
private:
|
||||
typedef std::vector<P3D_object *> Elements;
|
||||
Elements _elements;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConcreteStruct.cxx
|
||||
* @author drose
|
||||
* @date 2009-07-14
|
||||
*/
|
||||
|
||||
#include "p3dConcreteStruct.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DConcreteStruct::
|
||||
P3DConcreteStruct() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DConcreteStruct::
|
||||
~P3DConcreteStruct() {
|
||||
Elements::iterator ei;
|
||||
for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
|
||||
P3D_OBJECT_DECREF((*ei).second);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fundamental type of this kind of object.
|
||||
*/
|
||||
P3D_object_type P3DConcreteStruct::
|
||||
get_type() {
|
||||
return P3D_OT_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a boolean, if possible.
|
||||
*/
|
||||
bool P3DConcreteStruct::
|
||||
get_bool() {
|
||||
return !_elements.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the indicated C++ string object with the value of this object coerced
|
||||
* to a string.
|
||||
*/
|
||||
void P3DConcreteStruct::
|
||||
make_string(string &value) {
|
||||
std::ostringstream strm;
|
||||
strm << "{";
|
||||
if (!_elements.empty()) {
|
||||
Elements::iterator ei;
|
||||
ei = _elements.begin();
|
||||
strm << (*ei).first << ": " << *(*ei).second;
|
||||
++ei;
|
||||
while (ei != _elements.end()) {
|
||||
strm << ", " << (*ei).first << ": " << *(*ei).second;
|
||||
++ei;
|
||||
}
|
||||
}
|
||||
strm << "}";
|
||||
|
||||
value = strm.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the named property element in the object. The return value is a
|
||||
* new-reference P3D_object, or NULL on error.
|
||||
*/
|
||||
P3D_object *P3DConcreteStruct::
|
||||
get_property(const string &property) {
|
||||
Elements::const_iterator ei = _elements.find(property);
|
||||
if (ei != _elements.end()) {
|
||||
P3D_OBJECT_INCREF((*ei).second);
|
||||
return (*ei).second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies (or deletes, if value is NULL) the named property element in the
|
||||
* object. Returns true on success, false on failure.
|
||||
*/
|
||||
bool P3DConcreteStruct::
|
||||
set_property(const string &property, P3D_object *value) {
|
||||
if (value == nullptr) {
|
||||
// Delete an element.
|
||||
Elements::iterator ei = _elements.find(property);
|
||||
if (ei == _elements.end()) {
|
||||
// Invalid property.
|
||||
return false;
|
||||
}
|
||||
P3D_OBJECT_DECREF((*ei).second);
|
||||
_elements.erase(ei);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Replace or insert an element.
|
||||
P3D_OBJECT_INCREF(value);
|
||||
std::pair<Elements::iterator, bool> result = _elements.insert(Elements::value_type(property, value));
|
||||
if (!result.second) {
|
||||
// Replacing an element.
|
||||
Elements::iterator ei = result.first;
|
||||
P3D_OBJECT_DECREF((*ei).second);
|
||||
(*ei).second = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the named method exists on this object, false otherwise.
|
||||
*/
|
||||
bool P3DConcreteStruct::
|
||||
has_method(const string &method_name) {
|
||||
if (method_name == "toString") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the named method on the object, passing the indicated parameters.
|
||||
* If the method name is empty, invokes the object itself.
|
||||
*
|
||||
* If needs_response is true, the return value is a new-reference P3D_object
|
||||
* on success, or NULL on failure. If needs_response is false, the return
|
||||
* value is always NULL, and there is no way to determine success or failure.
|
||||
*/
|
||||
P3D_object *P3DConcreteStruct::
|
||||
call(const string &method_name, bool needs_response,
|
||||
P3D_object *params[], int num_params) {
|
||||
P3D_object *result = nullptr;
|
||||
|
||||
if (method_name == "toString") {
|
||||
string value;
|
||||
make_string(value);
|
||||
result = P3D_new_string_object(value.data(), value.length());
|
||||
}
|
||||
|
||||
if (result != nullptr && !needs_response) {
|
||||
P3D_OBJECT_DECREF(result);
|
||||
result = nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If this object has a valid XML representation for the indicated session
|
||||
* (that hasn't already been implemented by the generic code in P3DSession),
|
||||
* this method will apply it to the indicated "value" element and return true.
|
||||
* Otherwise, this method will leave the element unchanged and return false.
|
||||
*/
|
||||
bool P3DConcreteStruct::
|
||||
fill_xml(TiXmlElement *xvalue, P3DSession *session) {
|
||||
xvalue->SetAttribute("type", "concrete_struct");
|
||||
Elements::const_iterator ei;
|
||||
for (ei = _elements.begin(); ei != _elements.end(); ++ei) {
|
||||
TiXmlElement *xitem = session->p3dobj_to_xml((*ei).second);
|
||||
xitem->SetAttribute("key", (*ei).first);
|
||||
xvalue->LinkEndChild(xitem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConcreteStruct.h
|
||||
* @author drose
|
||||
* @date 2009-07-14
|
||||
*/
|
||||
|
||||
#ifndef P3DCONCRETESTRUCT_H
|
||||
#define P3DCONCRETESTRUCT_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dObject.h"
|
||||
|
||||
/**
|
||||
* A simple object that contains a standard mapping of string -> element. It
|
||||
* is passed by value between Python and Javascript, so it may be more optimal
|
||||
* for relatively small objects.
|
||||
*
|
||||
* Methods are not supported, other than built-in methods like toString().
|
||||
*/
|
||||
class P3DConcreteStruct : public P3DObject {
|
||||
public:
|
||||
P3DConcreteStruct();
|
||||
virtual ~P3DConcreteStruct();
|
||||
|
||||
virtual P3D_object_type get_type();
|
||||
virtual bool get_bool();
|
||||
|
||||
virtual void make_string(std::string &value);
|
||||
|
||||
virtual P3D_object *get_property(const std::string &property);
|
||||
virtual bool set_property(const std::string &property, P3D_object *value);
|
||||
|
||||
virtual bool has_method(const std::string &method_name);
|
||||
virtual P3D_object *call(const std::string &method_name, bool needs_response,
|
||||
P3D_object *params[], int num_params);
|
||||
|
||||
virtual bool fill_xml(TiXmlElement *xvalue, P3DSession *session);
|
||||
|
||||
private:
|
||||
typedef std::map<std::string, P3D_object *> Elements;
|
||||
Elements _elements;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConditionVar.I
|
||||
* @author drose
|
||||
* @date 2009-07-02
|
||||
*/
|
||||
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConditionVar.cxx
|
||||
* @author drose
|
||||
* @date 2009-07-02
|
||||
*/
|
||||
|
||||
#include "p3dConditionVar.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/time.h>
|
||||
#include <math.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DConditionVar::
|
||||
P3DConditionVar() {
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(&_lock);
|
||||
|
||||
// Create an auto-reset event.
|
||||
_event_signal = CreateEvent(nullptr, false, false, nullptr);
|
||||
|
||||
#else // _WIN32
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||
int result = pthread_mutex_init(&_lock, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
assert(result == 0);
|
||||
|
||||
result = pthread_cond_init(&_cvar, nullptr);
|
||||
assert(result == 0);
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DConditionVar::
|
||||
~P3DConditionVar() {
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(&_lock);
|
||||
CloseHandle(_event_signal);
|
||||
|
||||
#else // _WIN32
|
||||
int result = pthread_mutex_destroy(&_lock);
|
||||
assert(result == 0);
|
||||
|
||||
result = pthread_cond_destroy(&_cvar);
|
||||
assert(result == 0);
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the internal lock. The lock should be held during any calls to
|
||||
* wait() or notify().
|
||||
*/
|
||||
void P3DConditionVar::
|
||||
acquire() {
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(&_lock);
|
||||
|
||||
#else // _WIN32
|
||||
int result = pthread_mutex_lock(&_lock);
|
||||
assert(result == 0);
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires the lock to be held on entry. Releases the lock, waits for
|
||||
* another thread to call notify(), then reacquires the lock on exit.
|
||||
*/
|
||||
void P3DConditionVar::
|
||||
wait() {
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&_lock);
|
||||
|
||||
DWORD result = WaitForSingleObject(_event_signal, INFINITE);
|
||||
assert(result == WAIT_OBJECT_0);
|
||||
|
||||
EnterCriticalSection(&_lock);
|
||||
|
||||
#else // _WIN32
|
||||
int result = pthread_cond_wait(&_cvar, &_lock);
|
||||
assert(result == 0);
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
* As above, but waits no longer than timeout seconds before returning.
|
||||
*/
|
||||
void P3DConditionVar::
|
||||
wait(double timeout) {
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&_lock);
|
||||
|
||||
DWORD result = WaitForSingleObject(_event_signal, (DWORD)(timeout * 1000.0));
|
||||
assert(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT);
|
||||
|
||||
EnterCriticalSection(&_lock);
|
||||
#else // _WIN32
|
||||
|
||||
struct timeval now;
|
||||
gettimeofday(&now, nullptr);
|
||||
|
||||
// Convert from timeval to timespec
|
||||
struct timespec ts;
|
||||
ts.tv_sec = now.tv_sec;
|
||||
ts.tv_nsec = now.tv_usec * 1000;
|
||||
|
||||
int seconds = (int)floor(timeout);
|
||||
ts.tv_sec += seconds;
|
||||
ts.tv_nsec += (int)((timeout - seconds) * 1000000.0);
|
||||
if (ts.tv_nsec > 1000000) {
|
||||
ts.tv_nsec -= 1000000;
|
||||
++ts.tv_sec;
|
||||
}
|
||||
|
||||
int result = pthread_cond_timedwait(&_cvar, &_lock, &ts);
|
||||
if (result != 0 && result != ETIMEDOUT) {
|
||||
errno = result;
|
||||
perror("pthread_cond_timedwait");
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits a single thread blocked on wait(), if any. If no threads are
|
||||
* waiting, the event is lost. The lock should be held during this call.
|
||||
*/
|
||||
void P3DConditionVar::
|
||||
notify() {
|
||||
#ifdef _WIN32
|
||||
SetEvent(_event_signal);
|
||||
|
||||
#else // _WIN32
|
||||
int result = pthread_cond_signal(&_cvar);
|
||||
assert(result == 0);
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the internal lock.
|
||||
*/
|
||||
void P3DConditionVar::
|
||||
release() {
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(&_lock);
|
||||
|
||||
#else // _WIN32
|
||||
int result = pthread_mutex_unlock(&_lock);
|
||||
assert(result == 0);
|
||||
|
||||
#endif // _WIN32
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dConditionVar.h
|
||||
* @author drose
|
||||
* @date 2009-07-02
|
||||
*/
|
||||
|
||||
#ifndef P3DCONDITIONVAR_H
|
||||
#define P3DCONDITIONVAR_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
|
||||
/**
|
||||
* A simple condition-variable like object. It doesn't support the full
|
||||
* condition-var semantics, but it works well enough with one waiter and one
|
||||
* signaller.
|
||||
*/
|
||||
class P3DConditionVar {
|
||||
public:
|
||||
P3DConditionVar();
|
||||
~P3DConditionVar();
|
||||
|
||||
void acquire();
|
||||
void wait();
|
||||
void wait(double timeout);
|
||||
void notify();
|
||||
void release();
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
CRITICAL_SECTION _lock;
|
||||
HANDLE _event_signal;
|
||||
|
||||
#else // _WIN32
|
||||
pthread_mutex_t _lock;
|
||||
pthread_cond_t _cvar;
|
||||
#endif // _WIN32
|
||||
};
|
||||
|
||||
#include "p3dConditionVar.I"
|
||||
|
||||
#endif
|
||||
@@ -1,126 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dDownload.I
|
||||
* @author drose
|
||||
* @date 2009-06-11
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the URL that we are querying.
|
||||
*/
|
||||
const std::string &P3DDownload::
|
||||
get_url() const {
|
||||
return _url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the particular P3DInstance that is responsible for downloading
|
||||
* this object.
|
||||
*/
|
||||
inline void P3DDownload::
|
||||
set_instance(P3DInstance *instance) {
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the particular P3DInstance that is responsible for downloading this
|
||||
* object.
|
||||
*/
|
||||
inline P3DInstance *P3DDownload::
|
||||
get_instance() const {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an indication of the progress through the download file, 0.0 to
|
||||
* 1.0. Returns 1.0 if the size of the file is not known.
|
||||
*/
|
||||
inline double P3DDownload::
|
||||
get_download_progress() const {
|
||||
if (_total_expected_data == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (double)_total_data / (double)_total_expected_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the download progress is known, or false if it is unknown
|
||||
* because the server hasn't told us the total size it will be feeding us. If
|
||||
* this is false, get_download_progress() will generally always return 1.0;
|
||||
* use get_total_bytes() to measure progress in this case.
|
||||
*/
|
||||
inline bool P3DDownload::
|
||||
is_download_progress_known() const {
|
||||
return _progress_known;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of bytes downloaded so far.
|
||||
*/
|
||||
inline size_t P3DDownload::
|
||||
get_total_data() const {
|
||||
return _total_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of bytes expected to be downloaded. This is used to
|
||||
* compute the progress. Normally, this can be set from the download server,
|
||||
* but there may be cases when the download server doesn't accurately report
|
||||
* it.
|
||||
*/
|
||||
inline void P3DDownload::
|
||||
set_total_expected_data(size_t expected_data) {
|
||||
_total_expected_data = expected_data;
|
||||
_progress_known = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the download has finished, either successfully or
|
||||
* otherwise, or false if it is still in progress.
|
||||
*/
|
||||
inline bool P3DDownload::
|
||||
get_download_finished() const {
|
||||
return _status != P3D_RC_in_progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the download has finished successfully, or false if it is
|
||||
* still in progress or if it has failed.
|
||||
*/
|
||||
inline bool P3DDownload::
|
||||
get_download_success() const {
|
||||
return _status == P3D_RC_done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the download has failed because the instance is about to be
|
||||
* shut down, or false if it hasn't failed, or failed for some other reason.
|
||||
*/
|
||||
inline bool P3DDownload::
|
||||
get_download_terminated() const {
|
||||
return _status == P3D_RC_shutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called only by P3DInstance to set a unique ID for this particular download
|
||||
* object.
|
||||
*/
|
||||
inline void P3DDownload::
|
||||
set_download_id(int download_id) {
|
||||
_download_id = download_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique ID set by the P3DInstance.
|
||||
*/
|
||||
inline int P3DDownload::
|
||||
get_download_id() const {
|
||||
return _download_id;
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dDownload.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-11
|
||||
*/
|
||||
|
||||
#include "p3dDownload.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DDownload::
|
||||
P3DDownload() {
|
||||
_status = P3D_RC_in_progress;
|
||||
_http_status_code = 0;
|
||||
_total_data = 0;
|
||||
_total_expected_data = 0;
|
||||
_last_reported_time = 0;
|
||||
_progress_known = false;
|
||||
|
||||
_canceled = false;
|
||||
_download_id = 0;
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DDownload::
|
||||
P3DDownload(const P3DDownload ©) :
|
||||
_url(copy._url),
|
||||
_total_expected_data(copy._total_expected_data),
|
||||
_progress_known(copy._progress_known)
|
||||
{
|
||||
_status = P3D_RC_in_progress;
|
||||
_http_status_code = 0;
|
||||
_total_data = 0;
|
||||
_last_reported_time = 0;
|
||||
|
||||
_canceled = false;
|
||||
_download_id = 0;
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DDownload::
|
||||
~P3DDownload() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the source URL for the download.
|
||||
*/
|
||||
void P3DDownload::
|
||||
set_url(const std::string &url) {
|
||||
_url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a running download. download_finished() will not be called, but
|
||||
* the P3DDownload object itself will eventually be deleted by its owning
|
||||
* P3DInstance.
|
||||
*/
|
||||
void P3DDownload::
|
||||
cancel() {
|
||||
_canceled = true;
|
||||
if (_status == P3D_RC_in_progress) {
|
||||
_status = P3D_RC_generic_error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the download to its initial state, for re-trying the same download.
|
||||
*/
|
||||
void P3DDownload::
|
||||
clear() {
|
||||
_status = P3D_RC_in_progress;
|
||||
_http_status_code = 0;
|
||||
_total_data = 0;
|
||||
_last_reported_time = 0;
|
||||
|
||||
_canceled = false;
|
||||
_download_id = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by P3DInstance as more data arrives from the host. Returns true on
|
||||
* success, false if the download should be aborted.
|
||||
*/
|
||||
bool P3DDownload::
|
||||
feed_url_stream(P3D_result_code result_code,
|
||||
int http_status_code,
|
||||
size_t total_expected_data,
|
||||
const unsigned char *this_data,
|
||||
size_t this_data_size) {
|
||||
if (_canceled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_status = result_code;
|
||||
_http_status_code = http_status_code;
|
||||
|
||||
if (this_data_size != 0) {
|
||||
if (!receive_data(this_data, this_data_size)) {
|
||||
// Failure writing to disk or some such.
|
||||
_status = P3D_RC_generic_error;
|
||||
download_finished(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
_total_data += this_data_size;
|
||||
}
|
||||
|
||||
total_expected_data = std::max(total_expected_data, _total_data);
|
||||
if (total_expected_data > _total_expected_data) {
|
||||
// If the expected data grows during the download, we don't really know
|
||||
// how much we're getting.
|
||||
_progress_known = false;
|
||||
_total_expected_data = total_expected_data;
|
||||
}
|
||||
if (_total_expected_data > 0 &&
|
||||
(double)_total_data / (double)_total_expected_data < 0.9) {
|
||||
// But if we're not close to our target yet, let's say we do know (at
|
||||
// least until we get there and the target moves again).
|
||||
_progress_known = true;
|
||||
}
|
||||
|
||||
download_progress();
|
||||
|
||||
if (_status != P3D_RC_in_progress) {
|
||||
// We're done.
|
||||
if (get_download_success()) {
|
||||
_progress_known = true;
|
||||
}
|
||||
download_finished(get_download_success());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called as new data is downloaded. Returns true on success, false on
|
||||
* failure.
|
||||
*/
|
||||
bool P3DDownload::
|
||||
receive_data(const unsigned char *this_data, size_t this_data_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended to be overloaded to generate an occasional callback as new data
|
||||
* comes in.
|
||||
*/
|
||||
void P3DDownload::
|
||||
download_progress() {
|
||||
time_t now = time(nullptr);
|
||||
if (now - _last_reported_time > 10) {
|
||||
_last_reported_time = now;
|
||||
nout << "Downloading " << get_url() << ": ";
|
||||
if (_progress_known) {
|
||||
nout << int(get_download_progress() * 1000.0) / 10.0 << "%";
|
||||
} else {
|
||||
nout << int((double)get_total_data() / 104857.6) / 10.0 << "M";
|
||||
}
|
||||
nout << ", " << this << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended to be overloaded to generate a callback when the download
|
||||
* finishes, either successfully or otherwise. The bool parameter is true if
|
||||
* the download was successful.
|
||||
*/
|
||||
void P3DDownload::
|
||||
download_finished(bool success) {
|
||||
nout << "Downloaded " << get_url() << ": ";
|
||||
if (_progress_known) {
|
||||
nout << int(get_download_progress() * 1000.0) / 10.0 << "%";
|
||||
} else {
|
||||
nout << int((double)get_total_data() / 104857.6) / 10.0 << "M";
|
||||
}
|
||||
nout << ", " << this << ", success = " << success << "\n";
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dDownload.h
|
||||
* @author drose
|
||||
* @date 2009-06-11
|
||||
*/
|
||||
|
||||
#ifndef P3DDOWNLOAD_H
|
||||
#define P3DDOWNLOAD_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dReferenceCount.h"
|
||||
class P3DInstance;
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* This represents a request to download a single file from a URL, with no
|
||||
* particular destination. It is intended to be used as an abstract base
|
||||
* class; to use it, subclass it and redefine the appropriate callback
|
||||
* methods.
|
||||
*/
|
||||
class P3DDownload : public P3DReferenceCount {
|
||||
public:
|
||||
P3DDownload();
|
||||
P3DDownload(const P3DDownload ©);
|
||||
virtual ~P3DDownload();
|
||||
|
||||
void set_url(const std::string &url);
|
||||
inline const std::string &get_url() const;
|
||||
|
||||
inline void set_instance(P3DInstance *instance);
|
||||
inline P3DInstance *get_instance() const;
|
||||
|
||||
inline double get_download_progress() const;
|
||||
inline bool is_download_progress_known() const;
|
||||
inline bool get_download_finished() const;
|
||||
inline bool get_download_success() const;
|
||||
inline bool get_download_terminated() const;
|
||||
inline size_t get_total_data() const;
|
||||
inline void set_total_expected_data(size_t expected_data);
|
||||
|
||||
void cancel();
|
||||
void clear();
|
||||
|
||||
public:
|
||||
// These are intended to be called only by P3DInstance.
|
||||
inline void set_download_id(int download_id);
|
||||
inline int get_download_id() const;
|
||||
|
||||
bool feed_url_stream(P3D_result_code result_code,
|
||||
int http_status_code,
|
||||
size_t total_expected_data,
|
||||
const unsigned char *this_data,
|
||||
size_t this_data_size);
|
||||
|
||||
protected:
|
||||
virtual bool receive_data(const unsigned char *this_data,
|
||||
size_t this_data_size);
|
||||
virtual void download_progress();
|
||||
virtual void download_finished(bool success);
|
||||
|
||||
protected:
|
||||
P3D_result_code _status;
|
||||
int _http_status_code;
|
||||
|
||||
size_t _total_data;
|
||||
size_t _total_expected_data;
|
||||
time_t _last_reported_time;
|
||||
bool _progress_known;
|
||||
|
||||
private:
|
||||
bool _canceled;
|
||||
int _download_id;
|
||||
std::string _url;
|
||||
P3DInstance *_instance;
|
||||
};
|
||||
|
||||
#include "p3dDownload.I"
|
||||
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFileDownload.I
|
||||
* @author drose
|
||||
* @date 2009-06-11
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the filename that we are downloading into.
|
||||
*/
|
||||
const std::string &P3DFileDownload::
|
||||
get_filename() const {
|
||||
return _filename;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFileDownload.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-11
|
||||
*/
|
||||
|
||||
#include "p3dFileDownload.h"
|
||||
#include "p3dInstanceManager.h"
|
||||
#include "mkdir_complete.h"
|
||||
#include "wstring_encode.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DFileDownload::
|
||||
P3DFileDownload() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DFileDownload::
|
||||
P3DFileDownload(const P3DFileDownload ©) :
|
||||
P3DDownload(copy)
|
||||
{
|
||||
// We don't copy the filename. You have to copy it yourself.
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the target local filename for the download. Returns true on
|
||||
* success, false on failure.
|
||||
*/
|
||||
bool P3DFileDownload::
|
||||
set_filename(const std::string &filename) {
|
||||
_filename = filename;
|
||||
|
||||
return open_file();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the local file for receiving the download. Returns true on success,
|
||||
* false on failure.
|
||||
*/
|
||||
bool P3DFileDownload::
|
||||
open_file() {
|
||||
if (!mkfile_complete(_filename, nout)) {
|
||||
nout << "Failed to create " << _filename << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
_file.clear();
|
||||
#ifdef _WIN32
|
||||
std::wstring filename_w;
|
||||
if (string_to_wstring(filename_w, _filename)) {
|
||||
_file.open(filename_w.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
}
|
||||
#else // _WIN32
|
||||
_file.open(_filename.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
#endif // _WIN32
|
||||
if (!_file) {
|
||||
nout << "Failed to open " << _filename << " in write mode\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the local file.
|
||||
*/
|
||||
void P3DFileDownload::
|
||||
close_file() {
|
||||
_file.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called as new data is downloaded. Returns true on success, false on
|
||||
* failure.
|
||||
*/
|
||||
bool P3DFileDownload::
|
||||
receive_data(const unsigned char *this_data, size_t this_data_size) {
|
||||
_file.write((const char *)this_data, this_data_size);
|
||||
|
||||
if (!_file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended to be overloaded to generate a callback when the download
|
||||
* finishes, either successfully or otherwise. The bool parameter is true if
|
||||
* the download was successful.
|
||||
*/
|
||||
void P3DFileDownload::
|
||||
download_finished(bool success) {
|
||||
P3DDownload::download_finished(success);
|
||||
_file.close();
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFileDownload.h
|
||||
* @author drose
|
||||
* @date 2009-06-11
|
||||
*/
|
||||
|
||||
#ifndef P3DFILEDOWNLOAD_H
|
||||
#define P3DFILEDOWNLOAD_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dDownload.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
/**
|
||||
* This is a specialization on P3DDownload that specifically writes a disk
|
||||
* file.
|
||||
*/
|
||||
class P3DFileDownload : public P3DDownload {
|
||||
public:
|
||||
P3DFileDownload();
|
||||
P3DFileDownload(const P3DFileDownload ©);
|
||||
|
||||
bool set_filename(const std::string &filename);
|
||||
inline const std::string &get_filename() const;
|
||||
|
||||
protected:
|
||||
virtual bool open_file();
|
||||
void close_file();
|
||||
virtual bool receive_data(const unsigned char *this_data,
|
||||
size_t this_data_size);
|
||||
virtual void download_finished(bool success);
|
||||
|
||||
protected:
|
||||
ofstream _file;
|
||||
|
||||
private:
|
||||
std::string _filename;
|
||||
};
|
||||
|
||||
#include "p3dFileDownload.I"
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFileParams.I
|
||||
* @author drose
|
||||
* @date 2009-06-23
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the filename that was passed to set_p3d_filename().
|
||||
*/
|
||||
inline const std::string &P3DFileParams::
|
||||
get_p3d_filename() const {
|
||||
return _p3d_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the p3d file offset, the location in the file where the p3d data
|
||||
* starts.
|
||||
*/
|
||||
inline int P3DFileParams::
|
||||
get_p3d_offset() const {
|
||||
return _p3d_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string that was passed to set_p3d_url().
|
||||
*/
|
||||
inline const std::string &P3DFileParams::
|
||||
get_p3d_url() const {
|
||||
return _p3d_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of tokens in the params.
|
||||
*/
|
||||
inline int P3DFileParams::
|
||||
get_num_tokens() const {
|
||||
return _tokens.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyword of the nth token.
|
||||
*/
|
||||
inline const std::string &P3DFileParams::
|
||||
get_token_keyword(int n) const {
|
||||
assert(n >= 0 && n < (int)_tokens.size());
|
||||
return _tokens[n]._keyword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the nth token.
|
||||
*/
|
||||
inline const std::string &P3DFileParams::
|
||||
get_token_value(int n) const {
|
||||
assert(n >= 0 && n < (int)_tokens.size());
|
||||
return _tokens[n]._value;
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFileParams.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-23
|
||||
*/
|
||||
|
||||
#include "p3dFileParams.h"
|
||||
#include <ctype.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DFileParams::
|
||||
P3DFileParams() : _p3d_offset(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DFileParams::
|
||||
P3DFileParams(const P3DFileParams ©) :
|
||||
_p3d_filename(copy._p3d_filename),
|
||||
_p3d_offset(copy._p3d_offset),
|
||||
_p3d_url(copy._p3d_url),
|
||||
_tokens(copy._tokens),
|
||||
_args(copy._args)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void P3DFileParams::
|
||||
operator = (const P3DFileParams &other) {
|
||||
_p3d_filename = other._p3d_filename;
|
||||
_p3d_offset = other._p3d_offset;
|
||||
_p3d_url = other._p3d_url;
|
||||
_tokens = other._tokens;
|
||||
_args = other._args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the file that contains the instance data.
|
||||
*/
|
||||
void P3DFileParams::
|
||||
set_p3d_filename(const string &p3d_filename) {
|
||||
_p3d_filename = p3d_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the location in the file where the p3d file data starts.
|
||||
*/
|
||||
void P3DFileParams::
|
||||
set_p3d_offset(const int &p3d_offset) {
|
||||
_p3d_offset = p3d_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the original URL that hosted the p3d file, if any. This is for
|
||||
* documentation purposes only; it is communicated to the child Panda3D
|
||||
* process.
|
||||
*/
|
||||
void P3DFileParams::
|
||||
set_p3d_url(const string &p3d_url) {
|
||||
_p3d_url = p3d_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all the tokens associated with the instance.
|
||||
*/
|
||||
void P3DFileParams::
|
||||
set_tokens(const P3D_token tokens[], size_t num_tokens) {
|
||||
_tokens.clear();
|
||||
|
||||
for (size_t i = 0; i < num_tokens; ++i) {
|
||||
set_token(tokens[i]._keyword, tokens[i]._value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an individual token value.
|
||||
*/
|
||||
void P3DFileParams::
|
||||
set_token(const char *keyword, const char *value) {
|
||||
Token token;
|
||||
if (keyword != nullptr) {
|
||||
// Make the token lowercase, since HTML is case-insensitive but we're not.
|
||||
for (const char *p = keyword; *p; ++p) {
|
||||
token._keyword += tolower(*p);
|
||||
}
|
||||
}
|
||||
if (value != nullptr) {
|
||||
token._value = value;
|
||||
}
|
||||
_tokens.push_back(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the command-line arguments associated with the instance.
|
||||
*/
|
||||
void P3DFileParams::
|
||||
set_args(int argc, const char *argv[]) {
|
||||
_args.clear();
|
||||
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
const char *arg = argv[i];
|
||||
if (arg == nullptr) {
|
||||
arg = "";
|
||||
}
|
||||
_args.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the first appearance of the named token,
|
||||
* or empty string if the token does not appear.
|
||||
*/
|
||||
string P3DFileParams::
|
||||
lookup_token(const string &keyword) const {
|
||||
Tokens::const_iterator ti;
|
||||
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
|
||||
if ((*ti)._keyword == keyword) {
|
||||
return (*ti)._value;
|
||||
}
|
||||
}
|
||||
|
||||
return string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer value associated with the first appearance of the named
|
||||
* token, or zero if the token does not appear or is not an integer.
|
||||
*/
|
||||
int P3DFileParams::
|
||||
lookup_token_int(const string &keyword) const {
|
||||
string value = lookup_token(keyword);
|
||||
return atoi(value.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the named token appears in the list, false otherwise.
|
||||
*/
|
||||
bool P3DFileParams::
|
||||
has_token(const string &keyword) const {
|
||||
Tokens::const_iterator ti;
|
||||
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
|
||||
if ((*ti)._keyword == keyword) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a newly-allocated XML structure that corresponds to the file
|
||||
* parameter data within this instance.
|
||||
*/
|
||||
TiXmlElement *P3DFileParams::
|
||||
make_xml() {
|
||||
TiXmlElement *xfparams = new TiXmlElement("fparams");
|
||||
|
||||
xfparams->SetAttribute("p3d_filename", _p3d_filename);
|
||||
xfparams->SetAttribute("p3d_offset", _p3d_offset);
|
||||
xfparams->SetAttribute("p3d_url", _p3d_url);
|
||||
|
||||
Tokens::const_iterator ti;
|
||||
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
|
||||
const Token &token = (*ti);
|
||||
TiXmlElement *xtoken = new TiXmlElement("token");
|
||||
xtoken->SetAttribute("keyword", token._keyword);
|
||||
xtoken->SetAttribute("value", token._value);
|
||||
xfparams->LinkEndChild(xtoken);
|
||||
}
|
||||
|
||||
Args::const_iterator ai;
|
||||
for (ai = _args.begin(); ai != _args.end(); ++ai) {
|
||||
TiXmlElement *xarg = new TiXmlElement("arg");
|
||||
xarg->SetAttribute("value", (*ai));
|
||||
xfparams->LinkEndChild(xarg);
|
||||
}
|
||||
|
||||
return xfparams;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFileParams.h
|
||||
* @author drose
|
||||
* @date 2009-06-23
|
||||
*/
|
||||
|
||||
#ifndef P3DFILEPARAMS_H
|
||||
#define P3DFILEPARAMS_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "get_tinyxml.h"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Encapsulates the file parameters: the p3d_filename, and extra tokens.
|
||||
*/
|
||||
class P3DFileParams {
|
||||
public:
|
||||
P3DFileParams();
|
||||
P3DFileParams(const P3DFileParams ©);
|
||||
void operator = (const P3DFileParams &other);
|
||||
|
||||
void set_p3d_filename(const std::string &p3d_filename);
|
||||
void set_p3d_offset(const int &p3d_offset);
|
||||
void set_p3d_url(const std::string &p3d_url);
|
||||
void set_tokens(const P3D_token tokens[], size_t num_tokens);
|
||||
void set_token(const char *keyword, const char *value);
|
||||
void set_args(int argc, const char *argv[]);
|
||||
|
||||
inline const std::string &get_p3d_filename() const;
|
||||
inline int get_p3d_offset() const;
|
||||
inline const std::string &get_p3d_url() const;
|
||||
std::string lookup_token(const std::string &keyword) const;
|
||||
int lookup_token_int(const std::string &keyword) const;
|
||||
bool has_token(const std::string &keyword) const;
|
||||
|
||||
inline int get_num_tokens() const;
|
||||
inline const std::string &get_token_keyword(int n) const;
|
||||
inline const std::string &get_token_value(int n) const;
|
||||
|
||||
TiXmlElement *make_xml();
|
||||
|
||||
private:
|
||||
class Token {
|
||||
public:
|
||||
std::string _keyword;
|
||||
std::string _value;
|
||||
};
|
||||
typedef std::vector<Token> Tokens;
|
||||
typedef std::vector<std::string> Args;
|
||||
|
||||
std::string _p3d_filename;
|
||||
int _p3d_offset;
|
||||
std::string _p3d_url;
|
||||
Tokens _tokens;
|
||||
Args _args;
|
||||
};
|
||||
|
||||
#include "p3dFileParams.I"
|
||||
|
||||
#endif
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFloatObject.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#include "p3dFloatObject.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DFloatObject::
|
||||
P3DFloatObject(double value) : _value(value) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DFloatObject::
|
||||
P3DFloatObject(const P3DFloatObject ©) :
|
||||
P3DObject(copy),
|
||||
_value(copy._value)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fundamental type of this kind of object.
|
||||
*/
|
||||
P3D_object_type P3DFloatObject::
|
||||
get_type() {
|
||||
return P3D_OT_float;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a boolean, if possible.
|
||||
*/
|
||||
bool P3DFloatObject::
|
||||
get_bool() {
|
||||
return (_value != 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to an integer, if possible.
|
||||
*/
|
||||
int P3DFloatObject::
|
||||
get_int() {
|
||||
return (int)_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a floating-point value, if possible.
|
||||
*/
|
||||
double P3DFloatObject::
|
||||
get_float() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the indicated C++ string object with the value of this object coerced
|
||||
* to a string.
|
||||
*/
|
||||
void P3DFloatObject::
|
||||
make_string(std::string &value) {
|
||||
std::ostringstream strm;
|
||||
strm << _value;
|
||||
value = strm.str();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dFloatObject.h
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#ifndef P3DFLOATOBJECT_H
|
||||
#define P3DFLOATOBJECT_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dObject.h"
|
||||
|
||||
/**
|
||||
* An object type that contains a floating-point value.
|
||||
*/
|
||||
class P3DFloatObject : public P3DObject {
|
||||
public:
|
||||
P3DFloatObject(double value);
|
||||
P3DFloatObject(const P3DFloatObject ©);
|
||||
|
||||
public:
|
||||
virtual P3D_object_type get_type();
|
||||
virtual bool get_bool();
|
||||
virtual int get_int();
|
||||
virtual double get_float();
|
||||
virtual void make_string(std::string &value);
|
||||
|
||||
private:
|
||||
double _value;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dHost.I
|
||||
* @author drose
|
||||
* @date 2009-08-21
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns true if the host_dir has already been set, false if not. If this
|
||||
* returns true it is safe to call get_host_dir().
|
||||
*/
|
||||
inline bool P3DHost::
|
||||
has_host_dir() const {
|
||||
return (!_host_dir.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local directory into which files downloaded from this host will
|
||||
* be installed. It may not be safe to call this before the host has fully
|
||||
* bootstrapped; if there is some danger of calling this early in the
|
||||
* initialization process, you should check has_host_dir() first.
|
||||
*/
|
||||
inline const std::string &P3DHost::
|
||||
get_host_dir() const {
|
||||
assert(has_host_dir());
|
||||
return _host_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root URL of this particular host, as passed from the package
|
||||
* file. This is a unique string that identifies each host.
|
||||
*/
|
||||
inline const std::string &P3DHost::
|
||||
get_host_url() const {
|
||||
return _host_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root URL of this host, for constructing the URL to download
|
||||
* contents.xml only. This is the same as get_host_url(), except it is
|
||||
* guaranteed to end in a slash character.
|
||||
*
|
||||
* Also see get_download_url_prefix().
|
||||
*/
|
||||
inline const std::string &P3DHost::
|
||||
get_host_url_prefix() const {
|
||||
return _host_url_prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root URL of this host, for downloading everything other than
|
||||
* the contents.xml file. This is often the same as get_host_url_prefix(),
|
||||
* but it may be different in the case of an https server for contents.xml.
|
||||
*/
|
||||
inline const std::string &P3DHost::
|
||||
get_download_url_prefix() const {
|
||||
return _download_url_prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the descriptive name provided for this host, if any. Returns the
|
||||
* url if no descriptive name is provided. This will be available after
|
||||
* read_contents_file() has been called.
|
||||
*/
|
||||
inline const std::string &P3DHost::
|
||||
get_descriptive_name() const {
|
||||
return _descriptive_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a contents.xml file has been successfully read for this
|
||||
* host, false otherwise.
|
||||
*/
|
||||
inline bool P3DHost::
|
||||
has_contents_file() const {
|
||||
return (_xcontents != nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number that increments whenever a new version of the contents.xml
|
||||
* file has been read. This number is local to the session only; it has
|
||||
* nothing to do with the "seq" value written into the contents.xml file
|
||||
* itself.
|
||||
*
|
||||
* This can be used by packages to determine whether they need to redownload
|
||||
* from scratch.
|
||||
*/
|
||||
inline int P3DHost::
|
||||
get_contents_iseq() const {
|
||||
return _contents_iseq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the indicated pathname has the same md5 hash as the
|
||||
* contents.xml file (as provided by the server), false otherwise.
|
||||
*/
|
||||
inline bool P3DHost::
|
||||
check_contents_hash(const std::string &pathname) const {
|
||||
return _contents_spec.check_hash(pathname);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dHost.h
|
||||
* @author drose
|
||||
* @date 2009-08-21
|
||||
*/
|
||||
|
||||
#ifndef P3DHOST_H
|
||||
#define P3DHOST_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "fileSpec.h"
|
||||
#include <map>
|
||||
|
||||
class FileSpec;
|
||||
class P3DInstanceManager;
|
||||
class P3DPackage;
|
||||
|
||||
/**
|
||||
* Represents a particular download host serving up Panda3D packages.
|
||||
*/
|
||||
class P3DHost {
|
||||
private:
|
||||
P3DHost(const std::string &host_url, const std::string &host_dir = "");
|
||||
~P3DHost();
|
||||
|
||||
public:
|
||||
inline bool has_host_dir() const;
|
||||
inline const std::string &get_host_dir() const;
|
||||
inline const std::string &get_host_url() const;
|
||||
inline const std::string &get_host_url_prefix() const;
|
||||
inline const std::string &get_download_url_prefix() const;
|
||||
inline const std::string &get_descriptive_name() const;
|
||||
|
||||
P3DHost *get_alt_host(const std::string &alt_host);
|
||||
|
||||
inline bool has_contents_file() const;
|
||||
bool has_current_contents_file(P3DInstanceManager *inst_mgr) const;
|
||||
inline int get_contents_iseq() const;
|
||||
inline bool check_contents_hash(const std::string &pathname) const;
|
||||
|
||||
bool read_contents_file();
|
||||
bool read_contents_file(const std::string &contents_filename, bool fresh_download);
|
||||
void read_xhost(TiXmlElement *xhost);
|
||||
|
||||
P3DPackage *get_package(const std::string &package_name,
|
||||
const std::string &package_version,
|
||||
const std::string &package_platform,
|
||||
const std::string &package_seq,
|
||||
const std::string &alt_host = "");
|
||||
bool choose_suitable_platform(std::string &selected_platform,
|
||||
bool &per_platform,
|
||||
const std::string &package_name,
|
||||
const std::string &package_version,
|
||||
const std::string &package_platform);
|
||||
bool get_package_desc_file(FileSpec &desc_file,
|
||||
std::string &package_seq,
|
||||
bool &package_solo,
|
||||
const std::string &package_name,
|
||||
const std::string &package_version,
|
||||
const std::string &package_platform);
|
||||
|
||||
void forget_package(P3DPackage *package, const std::string &alt_host = "");
|
||||
void migrate_package_host(P3DPackage *package, const std::string &alt_host, P3DHost *new_host);
|
||||
|
||||
void choose_random_mirrors(std::vector<std::string> &result, int num_mirrors);
|
||||
void add_mirror(std::string mirror_url);
|
||||
|
||||
void uninstall();
|
||||
|
||||
private:
|
||||
void determine_host_dir(const std::string &host_dir_basename);
|
||||
|
||||
static std::string standardize_filename(const std::string &filename);
|
||||
static bool copy_file(const std::string &from_filename, const std::string &to_filename);
|
||||
static bool save_xml_file(TiXmlDocument *doc, const std::string &to_filename);
|
||||
static int compare_seq(const std::string &seq_a, const std::string &seq_b);
|
||||
static int compare_seq_int(const char *&num_a, const char *&num_b);
|
||||
|
||||
private:
|
||||
std::string _host_dir;
|
||||
std::string _host_url;
|
||||
std::string _host_url_prefix;
|
||||
std::string _download_url_prefix;
|
||||
std::string _descriptive_name;
|
||||
TiXmlElement *_xcontents;
|
||||
time_t _contents_expiration;
|
||||
int _contents_iseq;
|
||||
FileSpec _contents_spec;
|
||||
|
||||
typedef std::vector<std::string> Mirrors;
|
||||
Mirrors _mirrors;
|
||||
|
||||
typedef std::map<std::string, std::string> AltHosts;
|
||||
AltHosts _alt_hosts;
|
||||
|
||||
typedef std::vector<P3DPackage *> PlatformPackages;
|
||||
typedef std::map<std::string, PlatformPackages> PackageMap;
|
||||
typedef std::map<std::string, PackageMap> Packages;
|
||||
Packages _packages;
|
||||
typedef std::vector<P3DPackage *> FailedPackages;
|
||||
FailedPackages _failed_packages;
|
||||
|
||||
friend class P3DInstanceManager;
|
||||
};
|
||||
|
||||
#include "p3dHost.I"
|
||||
|
||||
#endif
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dInstance.I
|
||||
* @author drose
|
||||
* @date 2009-05-29
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the current file parameters.
|
||||
*/
|
||||
inline const P3DFileParams &P3DInstance::
|
||||
get_fparams() const {
|
||||
return _fparams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current window parameters.
|
||||
*/
|
||||
inline const P3DWindowParams &P3DInstance::
|
||||
get_wparams() const {
|
||||
return _wparams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique integer for each instance in the system.
|
||||
*/
|
||||
inline int P3DInstance::
|
||||
get_instance_id() const {
|
||||
return _instance_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that uniquely identifies this session. This is a
|
||||
* constructed string that includes the supplied session_name, the python and
|
||||
* panda version, and the publisher, as well as any other relevant details; it
|
||||
* is guaranteed to be unique for each unique session required for different
|
||||
* P3DInstances.
|
||||
*/
|
||||
inline const std::string &P3DInstance::
|
||||
get_session_key() const {
|
||||
return _session_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform of this particular session. Before the panda3d
|
||||
* package has been seen, this is the empty string; once we have downloaded
|
||||
* the info file for the panda3d package, it is filled in with whatever
|
||||
* platform is provided (that we're also runtime-compatible with).
|
||||
*
|
||||
* Presumably all of the platform-specific packages that are downloaded
|
||||
* subsequently must be of the exact same platform.
|
||||
*/
|
||||
inline const std::string &P3DInstance::
|
||||
get_session_platform() const {
|
||||
return _session_platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the P3DSession that is hosting this instance, or NULL if the
|
||||
* instance is not running.
|
||||
*/
|
||||
inline P3DSession *P3DInstance::
|
||||
get_session() const {
|
||||
return _session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the asynchronous notification function that was passed
|
||||
* to the constructor, if any, or NULL if asynchronous notifications are not
|
||||
* required.
|
||||
*/
|
||||
inline P3D_request_ready_func *P3DInstance::
|
||||
get_request_ready_func() const {
|
||||
return _func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance's p3d file is trusted and ready to launch,
|
||||
* false if it needs to be approved by the user.
|
||||
*/
|
||||
inline bool P3DInstance::
|
||||
is_trusted() const {
|
||||
return _p3d_trusted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance is allowed to be scripted by its embedding
|
||||
* web page, false otherwise. This may not be known until the p3d file has
|
||||
* been fully downloaded and opened.
|
||||
*/
|
||||
inline bool P3DInstance::
|
||||
get_matches_script_origin() const {
|
||||
return _matches_script_origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance has already been started within some session,
|
||||
* false otherwise.
|
||||
*/
|
||||
inline bool P3DInstance::
|
||||
is_started() const {
|
||||
return (_session != nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this instance has tried and failed to launch for some
|
||||
* reason.
|
||||
*/
|
||||
inline bool P3DInstance::
|
||||
is_failed() const {
|
||||
return _failed;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inline P3DInstance::ImageFile::
|
||||
ImageFile() {
|
||||
_use_standard_image = true;
|
||||
_temp_filename = nullptr;
|
||||
_image_placement = P3DSplashWindow::IP_none;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inline P3DInstance::ImageFile::
|
||||
~ImageFile() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the temporary file, if any.
|
||||
*/
|
||||
inline void P3DInstance::ImageFile::
|
||||
cleanup() {
|
||||
if (_temp_filename != nullptr) {
|
||||
delete _temp_filename;
|
||||
_temp_filename = nullptr;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,385 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dInstance.h
|
||||
* @author drose
|
||||
* @date 2009-05-29
|
||||
*/
|
||||
|
||||
#ifndef P3DINSTANCE_H
|
||||
#define P3DINSTANCE_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dFileDownload.h"
|
||||
#include "p3dFileParams.h"
|
||||
#include "p3dWindowParams.h"
|
||||
#include "p3dReferenceCount.h"
|
||||
#include "p3dSplashWindow.h"
|
||||
#include "p3dTemporaryFile.h"
|
||||
#include "p3dMultifileReader.h"
|
||||
#include "get_tinyxml.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "subprocessWindowBuffer.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
|
||||
class P3DSession;
|
||||
class P3DSplashWindow;
|
||||
class P3DDownload;
|
||||
class P3DPackage;
|
||||
class P3DObject;
|
||||
class P3DMainObject;
|
||||
class P3DTemporaryFile;
|
||||
|
||||
/**
|
||||
* This is an instance of a Panda3D window, as seen in the parent-level
|
||||
* process.
|
||||
*/
|
||||
class P3DInstance : public P3D_instance, public P3DReferenceCount {
|
||||
public:
|
||||
P3DInstance(P3D_request_ready_func *func,
|
||||
const P3D_token tokens[], size_t num_tokens,
|
||||
int argc, const char *argv[], void *user_data);
|
||||
~P3DInstance();
|
||||
void cleanup();
|
||||
|
||||
void set_p3d_url(const std::string &p3d_url);
|
||||
void set_p3d_filename(const std::string &p3d_filename, const int &p3d_offset = 0);
|
||||
int make_p3d_stream(const std::string &p3d_url);
|
||||
inline const P3DFileParams &get_fparams() const;
|
||||
|
||||
void set_wparams(const P3DWindowParams &wparams);
|
||||
inline const P3DWindowParams &get_wparams() const;
|
||||
|
||||
P3D_object *get_panda_script_object() const;
|
||||
void set_browser_script_object(P3D_object *object);
|
||||
|
||||
bool has_request();
|
||||
P3D_request *get_request();
|
||||
void bake_requests();
|
||||
void add_raw_request(TiXmlDocument *doc);
|
||||
void add_baked_request(P3D_request *request);
|
||||
static void finish_request(P3D_request *request, bool handled);
|
||||
|
||||
bool feed_url_stream(int unique_id,
|
||||
P3D_result_code result_code,
|
||||
int http_status_code,
|
||||
size_t total_expected_data,
|
||||
const unsigned char *this_data,
|
||||
size_t this_data_size);
|
||||
|
||||
bool handle_event(const P3D_event_data &event);
|
||||
|
||||
inline int get_instance_id() const;
|
||||
inline const std::string &get_session_key() const;
|
||||
const std::string &get_log_pathname() const;
|
||||
inline const std::string &get_session_platform() const;
|
||||
|
||||
inline P3DSession *get_session() const;
|
||||
|
||||
inline P3D_request_ready_func *get_request_ready_func() const;
|
||||
|
||||
void add_package(const std::string &name, const std::string &version,
|
||||
const std::string &seq, P3DHost *host);
|
||||
void add_package(P3DPackage *package);
|
||||
void remove_package(P3DPackage *package);
|
||||
bool get_packages_info_ready() const;
|
||||
bool get_packages_ready() const;
|
||||
bool get_packages_failed() const;
|
||||
|
||||
inline bool is_trusted() const;
|
||||
inline bool get_matches_script_origin() const;
|
||||
int start_download(P3DDownload *download, bool add_request = true);
|
||||
inline bool is_started() const;
|
||||
inline bool is_failed() const;
|
||||
void request_stop_sub_thread();
|
||||
void request_stop_main_thread();
|
||||
void request_refresh();
|
||||
void request_callback(P3D_callback_func *func, void *data);
|
||||
|
||||
TiXmlElement *make_xml();
|
||||
void splash_button_clicked_sub_thread();
|
||||
void splash_button_clicked_main_thread();
|
||||
void auth_button_clicked();
|
||||
void play_button_clicked();
|
||||
|
||||
void auth_finished_sub_thread();
|
||||
void auth_finished_main_thread();
|
||||
|
||||
bool uninstall_packages();
|
||||
bool uninstall_host();
|
||||
|
||||
private:
|
||||
class ImageDownload : public P3DFileDownload {
|
||||
public:
|
||||
ImageDownload(P3DInstance *inst, int index);
|
||||
protected:
|
||||
virtual void download_finished(bool success);
|
||||
private:
|
||||
P3DInstance *_inst;
|
||||
int _index;
|
||||
};
|
||||
class InstanceDownload : public P3DFileDownload {
|
||||
public:
|
||||
InstanceDownload(P3DInstance *inst);
|
||||
protected:
|
||||
virtual void download_progress();
|
||||
virtual void download_finished(bool success);
|
||||
private:
|
||||
P3DInstance *_inst;
|
||||
};
|
||||
|
||||
// The different kinds of image files we download for the splash window.
|
||||
enum ImageType {
|
||||
// Also update _image_type_names when you update this list.
|
||||
IT_download,
|
||||
IT_unauth,
|
||||
IT_ready,
|
||||
IT_failed,
|
||||
IT_launch,
|
||||
IT_active,
|
||||
IT_auth_ready,
|
||||
IT_auth_rollover,
|
||||
IT_auth_click,
|
||||
IT_play_ready,
|
||||
IT_play_rollover,
|
||||
IT_play_click,
|
||||
IT_none, // Must be the last value
|
||||
IT_num_image_types, // Not a real value
|
||||
};
|
||||
|
||||
void priv_set_p3d_filename(const std::string &p3d_filename, const int &p3d_offset = -1);
|
||||
void determine_p3d_basename(const std::string &p3d_url);
|
||||
|
||||
bool check_matches_origin(const std::string &origin_match);
|
||||
bool check_matches_origin_one(const std::string &origin_match);
|
||||
bool check_matches_hostname(const std::string &orig, const std::string &match);
|
||||
void separate_components(std::vector<std::string> &components, const std::string &str);
|
||||
bool check_matches_component(const std::string &orig, const std::string &match);
|
||||
|
||||
void check_p3d_signature();
|
||||
void mark_p3d_untrusted();
|
||||
void mark_p3d_trusted();
|
||||
void scan_app_desc_file(TiXmlDocument *doc);
|
||||
void add_panda3d_package();
|
||||
void add_packages();
|
||||
std::string find_alt_host_url(const std::string &host_url, const std::string &alt_host);
|
||||
void get_host_info(P3DHost *host);
|
||||
std::string get_start_dir_suffix() const;
|
||||
|
||||
void send_browser_script_object();
|
||||
P3D_request *make_p3d_request(TiXmlElement *xrequest);
|
||||
void handle_notify_request(const std::string &message);
|
||||
void handle_script_request(const std::string &operation, P3D_object *object,
|
||||
const std::string &property_name, P3D_object *value,
|
||||
bool needs_response, int unique_id);
|
||||
|
||||
void set_failed();
|
||||
void make_splash_window();
|
||||
void set_background_image(ImageType image_type);
|
||||
void set_button_image(ImageType image_type);
|
||||
void report_package_info_ready(P3DPackage *package);
|
||||
void consider_start_download();
|
||||
void ready_to_install();
|
||||
void start_next_download();
|
||||
void mark_download_complete();
|
||||
void ready_to_start();
|
||||
void report_instance_progress(double progress, bool is_progress_known,
|
||||
size_t received_data);
|
||||
void report_package_progress(P3DPackage *package, double progress);
|
||||
void report_package_done(P3DPackage *package, bool success);
|
||||
void set_install_label(const std::string &install_label);
|
||||
|
||||
void paint_window();
|
||||
|
||||
#ifdef __APPLE__
|
||||
bool get_framebuffer_osx_port();
|
||||
bool get_framebuffer_osx_cgcontext();
|
||||
void paint_window_osx_port();
|
||||
void paint_window_osx_cgcontext(CGContextRef context);
|
||||
#endif // __APPLE__
|
||||
|
||||
bool handle_event_osx_event_record(const P3D_event_data &event);
|
||||
bool handle_event_osx_cocoa(const P3D_event_data &event);
|
||||
void add_carbon_modifier_flags(unsigned int &swb_flags, int modifiers);
|
||||
void add_cocoa_modifier_flags(unsigned int &swb_flags, int modifiers);
|
||||
|
||||
void send_notify(const std::string &message);
|
||||
|
||||
#ifdef __APPLE__
|
||||
void alloc_swbuffer();
|
||||
void free_swbuffer();
|
||||
static void timer_callback(CFRunLoopTimerRef timer, void *info);
|
||||
#endif // __APPLE__
|
||||
|
||||
P3D_request_ready_func *_func;
|
||||
P3D_object *_dom_object;
|
||||
P3DMainObject *_main_object;
|
||||
std::string _p3d_basename;
|
||||
std::string _origin_protocol;
|
||||
std::string _origin_hostname;
|
||||
std::string _origin_port;
|
||||
|
||||
// We need a list of previous time reports so we can average the predicted
|
||||
// download time over the past few seconds.
|
||||
class TimeReport {
|
||||
public:
|
||||
double _total;
|
||||
double _report_time;
|
||||
};
|
||||
typedef std::deque<TimeReport> TimeReports;
|
||||
TimeReports _time_reports;
|
||||
double _total_time_reports;
|
||||
|
||||
P3DTemporaryFile *_temp_p3d_filename;
|
||||
|
||||
// For downloading the various images used by the splash window.
|
||||
P3DPackage *_image_package;
|
||||
static const char *_image_type_names[IT_num_image_types];
|
||||
|
||||
class ImageFile {
|
||||
public:
|
||||
inline ImageFile();
|
||||
inline ~ImageFile();
|
||||
inline void cleanup();
|
||||
|
||||
bool _use_standard_image;
|
||||
P3DTemporaryFile *_temp_filename;
|
||||
std::string _filename;
|
||||
P3DSplashWindow::ImagePlacement _image_placement;
|
||||
};
|
||||
ImageFile _image_files[IT_num_image_types];
|
||||
ImageType _current_background_image;
|
||||
ImageType _current_button_image;
|
||||
|
||||
bool _got_fparams;
|
||||
P3DFileParams _fparams;
|
||||
P3DMultifileReader _mf_reader;
|
||||
|
||||
bool _got_wparams;
|
||||
P3DWindowParams _wparams;
|
||||
|
||||
bool _p3d_trusted;
|
||||
TiXmlElement *_xpackage;
|
||||
|
||||
// Holds the list of certificates that are pre-approved by the plugin
|
||||
// vendor.
|
||||
P3DPackage *_certlist_package;
|
||||
|
||||
// For downloading the p3dcert authorization program.
|
||||
P3DPackage *_p3dcert_package;
|
||||
|
||||
int _instance_id;
|
||||
std::string _session_key;
|
||||
std::string _log_basename;
|
||||
std::string _session_platform;
|
||||
std::string _prc_name;
|
||||
std::string _start_dir;
|
||||
bool _hidden;
|
||||
bool _matches_run_origin;
|
||||
bool _matches_script_origin;
|
||||
bool _allow_python_dev;
|
||||
bool _keep_user_env;
|
||||
bool _auto_install;
|
||||
bool _auto_start;
|
||||
bool _stop_on_ready;
|
||||
bool _auth_button_clicked;
|
||||
bool _failed;
|
||||
|
||||
P3DSession *_session;
|
||||
P3DAuthSession *_auth_session;
|
||||
std::string _log_pathname;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// On OSX, we have to get a copy of the framebuffer data back from the child
|
||||
// process, and draw it to the window, here in the parent process. Crazy!
|
||||
int _shared_fd;
|
||||
size_t _shared_mmap_size;
|
||||
std::string _shared_filename;
|
||||
SubprocessWindowBuffer *_swbuffer;
|
||||
char *_reversed_buffer;
|
||||
CFDataRef _buffer_data;
|
||||
CGDataProviderRef _data_provider;
|
||||
CGColorSpaceRef _buffer_color_space;
|
||||
CGImageRef _buffer_image;
|
||||
|
||||
bool _mouse_active;
|
||||
unsigned int _modifiers;
|
||||
|
||||
CFRunLoopTimerRef _frame_timer;
|
||||
#endif // __APPLE__
|
||||
|
||||
P3DSplashWindow *_splash_window;
|
||||
std::string _install_label;
|
||||
bool _instance_window_opened;
|
||||
bool _instance_window_attached;
|
||||
bool _stuff_to_download;
|
||||
|
||||
// Keep track of when the download was started, for reporting purposes.
|
||||
// These members are used both for the instance download, and for the later
|
||||
// package download.
|
||||
#ifdef _WIN32
|
||||
int _start_dl_tick;
|
||||
#else
|
||||
struct timeval _start_dl_timeval;
|
||||
#endif
|
||||
|
||||
// This is set false initially, but true if the instance download continues
|
||||
// for more than a couple of seconds.
|
||||
bool _show_dl_instance_progress;
|
||||
|
||||
typedef std::vector<P3DPackage *> Packages;
|
||||
Packages _packages;
|
||||
Packages _downloading_packages;
|
||||
int _download_package_index;
|
||||
size_t _prev_downloaded;
|
||||
size_t _total_download_size;
|
||||
size_t _total_downloaded;
|
||||
bool _packages_specified;
|
||||
bool _download_started;
|
||||
bool _download_complete;
|
||||
bool _instance_started;
|
||||
|
||||
// We keep the _panda3d pointer separately because it's so important, but
|
||||
// it's in the above vector also.
|
||||
P3DPackage *_panda3d_package;
|
||||
|
||||
typedef std::map<int, P3DDownload *> Downloads;
|
||||
Downloads _downloads;
|
||||
|
||||
// The _raw_requests queue might be filled up by the read thread, so we
|
||||
// protect it in a lock.
|
||||
LOCK _request_lock;
|
||||
typedef std::deque<TiXmlDocument *> RawRequests;
|
||||
RawRequests _raw_requests;
|
||||
bool _requested_stop;
|
||||
|
||||
// The _baked_requests queue is only touched in the main thread; no lock
|
||||
// needed.
|
||||
typedef std::deque<P3D_request *> BakedRequests;
|
||||
BakedRequests _baked_requests;
|
||||
|
||||
friend class P3DSession;
|
||||
friend class P3DAuthSession;
|
||||
friend class ImageDownload;
|
||||
friend class InstanceDownload;
|
||||
friend class P3DWindowParams;
|
||||
friend class P3DPackage;
|
||||
};
|
||||
|
||||
#include "p3dInstance.I"
|
||||
|
||||
#endif
|
||||
@@ -1,322 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dInstanceManager.I
|
||||
* @author drose
|
||||
* @date 2009-05-29
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns true if the instance manager is successfully initialized, false
|
||||
* otherwise.
|
||||
*/
|
||||
inline bool P3DInstanceManager::
|
||||
is_initialized() const {
|
||||
return _is_initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreates the runtime environment if a previous call to uninstall_all()
|
||||
* removed it. Does nothing if the runtime environment is already correctly
|
||||
* set up.
|
||||
*/
|
||||
inline void P3DInstanceManager::
|
||||
reconsider_runtime_environment() {
|
||||
assert(_is_initialized);
|
||||
if (!_created_runtime_environment) {
|
||||
create_runtime_environment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the verify_contents setting. When this is set to P3D_VC_none, it
|
||||
* indicates that we don't need to contact the server to verify that a
|
||||
* contents.xml file is fresh before using it; we should just use it as it is.
|
||||
*/
|
||||
inline P3D_verify_contents P3DInstanceManager::
|
||||
get_verify_contents() const {
|
||||
return _verify_contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the verify_contents flag to P3D_VC_normal, if it is P3D_VC_none.
|
||||
* This should be done whenever we discover anything needs to be downloaded.
|
||||
* At this point, we might as well verify everything.
|
||||
*/
|
||||
inline void P3DInstanceManager::
|
||||
reset_verify_contents() {
|
||||
if (_verify_contents == P3D_VC_none) {
|
||||
_verify_contents = P3D_VC_normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the api_version number which was passed to P3D_initialize().
|
||||
* Client code may use this to determine how to interpret parameters to
|
||||
* various functions whose interface may have changed over different versions.
|
||||
*/
|
||||
inline int P3DInstanceManager::
|
||||
get_api_version() const {
|
||||
return _api_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard host_url which the instances should attempt to contact
|
||||
* to download auxiliary packages associated with the core API, such as the
|
||||
* p3dcert and images packages. This is normally the compiled-in
|
||||
* PANDA_PACKAGE_HOST_URL, but it might be set to something different by the
|
||||
* -u parameter on the panda3d executable.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_host_url() const {
|
||||
return _host_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory into which all the P3D runtime files are
|
||||
* downloaded and installed. This must be a writable directory or nothing
|
||||
* will work.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_root_dir() const {
|
||||
return _root_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory that the .p3d file should be mounted to and run from.
|
||||
* This is usually the "start" subdirectory of the root_dir.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_start_dir() const {
|
||||
return _start_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string that corresponds to the platform on which we are
|
||||
* running. This string will be used to determine the appropriate packages to
|
||||
* download.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_platform() const {
|
||||
return _platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pathname of the directory into which temporary files should be
|
||||
* written. This filename will end with a slash, so that full pathnames may
|
||||
* be made by concatenting directly with this string.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_temp_directory() const {
|
||||
return _temp_directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pathname of the directory into which all log files should be
|
||||
* written. This filename will end with a slash, so that full pathnames may
|
||||
* be made by concatenting directly with this string.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_log_directory() const {
|
||||
return _log_directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename of the system log file; this file is responsible for
|
||||
* downloading and installing updates, and launching applications. This is
|
||||
* different from the session log file(s), which represent the output from a
|
||||
* particular Python session.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_log_pathname() const {
|
||||
return _log_pathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the trusted_environment flag passed to the
|
||||
* constructor. If this is true, it means the environment we are running in
|
||||
* is trusted and the p3d file is already vetted. This means the current
|
||||
* working directory will remain unchanged, and the p3d file will be run
|
||||
* without checking its signature.
|
||||
*
|
||||
* This should generally be true only when run by panda3d.exe or panda3dw.exe,
|
||||
* and not when run by the web plugin.
|
||||
*/
|
||||
inline bool P3DInstanceManager::
|
||||
get_trusted_environment() const {
|
||||
return _trusted_environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the console_environment flag passed to the
|
||||
* constructor. If this is true, it means we are running from a text-based
|
||||
* console window, and not from a desktop environment.
|
||||
*
|
||||
* This should generally be true only when run by panda3d.exe, and not when
|
||||
* run by the web plugin or by panda3dw.exe.
|
||||
*/
|
||||
inline bool P3DInstanceManager::
|
||||
get_console_environment() const {
|
||||
return _console_environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of different supported platforms available in
|
||||
* get_supported_platform().
|
||||
*/
|
||||
inline int P3DInstanceManager::
|
||||
get_num_supported_platforms() const {
|
||||
return (int)_supported_platforms.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth supported platform, where 0 <= n <
|
||||
* get_num_supported_platforms().
|
||||
*
|
||||
* A given runtime environment may support multiple different platforms, e.g.
|
||||
* win32 or win64, with the restriction that all platform-specific packages
|
||||
* (beginning from panda3d), must be the same platform.
|
||||
*
|
||||
* This function enumerates the different platforms that the current runtime
|
||||
* environment will support, in order of preference--preferred platforms
|
||||
* appear first in the list.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_supported_platform(int n) const {
|
||||
return _supported_platforms.at(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin's reported major version number.
|
||||
*/
|
||||
inline int P3DInstanceManager::
|
||||
get_plugin_major_version() const {
|
||||
return _plugin_major_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin's reported minor version number.
|
||||
*/
|
||||
inline int P3DInstanceManager::
|
||||
get_plugin_minor_version() const {
|
||||
return _plugin_minor_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin's reported sequence version number.
|
||||
*/
|
||||
inline int P3DInstanceManager::
|
||||
get_plugin_sequence_version() const {
|
||||
return _plugin_sequence_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the plugin claims to be from an "official" build, and the
|
||||
* its version number is authoritative; or false if it makes no such claim
|
||||
* (for instance, it was built by someone checking out from cvs).
|
||||
*/
|
||||
inline bool P3DInstanceManager::
|
||||
get_plugin_official_version() const {
|
||||
return _plugin_official_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "distributor" reported by the plugin. This should represent
|
||||
* the entity that built and hosted the plugin.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_plugin_distributor() const {
|
||||
return _plugin_distributor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host URL from which this Core API was downloaded (according to
|
||||
* the plugin). This is for reporting purposes only; see get_host_url() for
|
||||
* the URL to contact to actually download content.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_coreapi_host_url() const {
|
||||
return _coreapi_host_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp associated with this Core API DLL (according to the
|
||||
* plugin). This is the timestamp shown in the contents.xml for this host,
|
||||
* and is usually the time at which the plugin was built.
|
||||
*/
|
||||
inline time_t P3DInstanceManager::
|
||||
get_coreapi_timestamp() const {
|
||||
return _coreapi_timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version number associated with the Core API, if provided. Some
|
||||
* early versions of the Core API, and some early versions of the plugin, did
|
||||
* not provide a number here. If provided, this will be a string of dot-
|
||||
* separated integers.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_coreapi_set_ver() const {
|
||||
return _coreapi_set_ver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "super mirror" URL. See p3d_plugin.h.
|
||||
*/
|
||||
inline const std::string &P3DInstanceManager::
|
||||
get_super_mirror() const {
|
||||
return _super_mirror_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of instances currently running within the world.
|
||||
*/
|
||||
inline int P3DInstanceManager::
|
||||
get_num_instances() const {
|
||||
return _instances.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton "undefined" object, as a new reference.
|
||||
*/
|
||||
inline P3D_object *P3DInstanceManager::
|
||||
new_undefined_object() {
|
||||
P3D_OBJECT_INCREF(_undefined_object);
|
||||
return _undefined_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton "none" object, as a new reference.
|
||||
*/
|
||||
inline P3D_object *P3DInstanceManager::
|
||||
new_none_object() {
|
||||
P3D_OBJECT_INCREF(_none_object);
|
||||
return _none_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton "true" or "false" object, as a new reference.
|
||||
*/
|
||||
inline P3D_object *P3DInstanceManager::
|
||||
new_bool_object(bool value) {
|
||||
P3D_object *obj = (value) ? _true_object : _false_object;
|
||||
P3D_OBJECT_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hex digit corresponding to the indicated integer value.
|
||||
*/
|
||||
inline char P3DInstanceManager::
|
||||
encode_hexdigit(int c) {
|
||||
if (c >= 10) {
|
||||
return c - 10 + 'a';
|
||||
}
|
||||
return c + '0';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dInstanceManager.h
|
||||
* @author drose
|
||||
* @date 2009-05-29
|
||||
*/
|
||||
|
||||
#ifndef P3DINSTANCEMANAGER_H
|
||||
#define P3DINSTANCEMANAGER_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dConditionVar.h"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#define OPENSSL_NO_KRB5
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/md5.h>
|
||||
|
||||
class P3DInstance;
|
||||
class P3DSession;
|
||||
class P3DAuthSession;
|
||||
class P3DHost;
|
||||
class P3DPackage;
|
||||
class FileSpec;
|
||||
class TiXmlElement;
|
||||
|
||||
/**
|
||||
* This global class manages the set of instances in the universe.
|
||||
*/
|
||||
class P3DInstanceManager {
|
||||
private:
|
||||
P3DInstanceManager();
|
||||
~P3DInstanceManager();
|
||||
|
||||
public:
|
||||
bool initialize(int api_version, const std::string &contents_filename,
|
||||
const std::string &host_url,
|
||||
P3D_verify_contents verify_contents,
|
||||
const std::string &platform,
|
||||
const std::string &log_directory,
|
||||
const std::string &log_basename,
|
||||
bool trusted_environment,
|
||||
bool console_environment,
|
||||
const std::string &root_dir = "",
|
||||
const std::string &host_dir = "",
|
||||
const std::string &start_dir = "");
|
||||
|
||||
inline bool is_initialized() const;
|
||||
inline void reconsider_runtime_environment();
|
||||
inline P3D_verify_contents get_verify_contents() const;
|
||||
inline void reset_verify_contents();
|
||||
|
||||
inline int get_api_version() const;
|
||||
inline const std::string &get_host_url() const;
|
||||
inline const std::string &get_root_dir() const;
|
||||
inline const std::string &get_start_dir() const;
|
||||
inline const std::string &get_platform() const;
|
||||
inline const std::string &get_temp_directory() const;
|
||||
inline const std::string &get_log_directory() const;
|
||||
inline const std::string &get_log_pathname() const;
|
||||
inline bool get_trusted_environment() const;
|
||||
inline bool get_console_environment() const;
|
||||
|
||||
inline int get_num_supported_platforms() const;
|
||||
inline const std::string &get_supported_platform(int n) const;
|
||||
|
||||
void set_plugin_version(int major, int minor, int sequence,
|
||||
bool official, const std::string &distributor,
|
||||
const std::string &coreapi_host_url,
|
||||
time_t coreapi_timestamp,
|
||||
const std::string &coreapi_set_ver);
|
||||
inline int get_plugin_major_version() const;
|
||||
inline int get_plugin_minor_version() const;
|
||||
inline int get_plugin_sequence_version() const;
|
||||
inline bool get_plugin_official_version() const;
|
||||
inline const std::string &get_plugin_distributor() const;
|
||||
inline const std::string &get_coreapi_host_url() const;
|
||||
inline time_t get_coreapi_timestamp() const;
|
||||
inline const std::string &get_coreapi_set_ver() const;
|
||||
|
||||
void set_super_mirror(const std::string &super_mirror_url);
|
||||
inline const std::string &get_super_mirror() const;
|
||||
|
||||
P3DInstance *
|
||||
create_instance(P3D_request_ready_func *func,
|
||||
const P3D_token tokens[], size_t num_tokens,
|
||||
int argc, const char *argv[], void *user_data);
|
||||
|
||||
bool set_p3d_filename(P3DInstance *inst, bool is_local,
|
||||
const std::string &p3d_filename, const int &p3d_offset);
|
||||
int make_p3d_stream(P3DInstance *inst, const std::string &p3d_url);
|
||||
bool start_instance(P3DInstance *inst);
|
||||
void finish_instance(P3DInstance *inst);
|
||||
P3DAuthSession *authorize_instance(P3DInstance *inst);
|
||||
|
||||
P3DInstance *validate_instance(P3D_instance *instance);
|
||||
|
||||
P3DInstance *check_request();
|
||||
void wait_request(double timeout);
|
||||
|
||||
P3DHost *get_host(const std::string &host_url);
|
||||
void forget_host(P3DHost *host);
|
||||
|
||||
inline int get_num_instances() const;
|
||||
|
||||
int get_unique_id();
|
||||
void signal_request_ready(P3DInstance *inst);
|
||||
|
||||
P3D_class_definition *make_class_definition() const;
|
||||
|
||||
inline P3D_object *new_undefined_object();
|
||||
inline P3D_object *new_none_object();
|
||||
inline P3D_object *new_bool_object(bool value);
|
||||
|
||||
std::string make_temp_filename(const std::string &extension);
|
||||
void release_temp_filename(const std::string &filename);
|
||||
|
||||
bool find_cert(X509 *cert);
|
||||
void read_certlist(P3DPackage *package);
|
||||
std::string get_cert_dir(X509 *cert);
|
||||
static std::string cert_to_der(X509 *cert);
|
||||
|
||||
void uninstall_all();
|
||||
|
||||
static P3DInstanceManager *get_global_ptr();
|
||||
static void delete_global_ptr();
|
||||
|
||||
static inline char encode_hexdigit(int c);
|
||||
static bool scan_directory(const std::string &dirname, std::vector<std::string> &contents);
|
||||
static bool scan_directory_recursively(const std::string &dirname,
|
||||
std::vector<std::string> &filename_contents,
|
||||
std::vector<std::string> &dirname_contents,
|
||||
const std::string &prefix = "");
|
||||
static void delete_directory_recursively(const std::string &root_dir);
|
||||
static bool remove_file_from_list(std::vector<std::string> &contents, const std::string &filename);
|
||||
|
||||
static void append_safe_dir(std::string &root, const std::string &basename);
|
||||
|
||||
private:
|
||||
void create_runtime_environment();
|
||||
static void append_safe_dir_component(std::string &root, const std::string &component);
|
||||
|
||||
private:
|
||||
// The notify thread. This thread runs only for the purpose of generating
|
||||
// asynchronous notifications of requests, to callers who ask for it.
|
||||
THREAD_CALLBACK_DECLARATION(P3DInstanceManager, nt_thread_run);
|
||||
void nt_thread_run();
|
||||
|
||||
#ifdef _WIN32
|
||||
static bool supports_win64();
|
||||
#endif // _WIN32
|
||||
|
||||
private:
|
||||
bool _is_initialized;
|
||||
bool _created_runtime_environment;
|
||||
int _api_version;
|
||||
std::string _host_url;
|
||||
std::string _root_dir;
|
||||
std::string _host_dir;
|
||||
std::string _start_dir;
|
||||
std::string _certs_dir;
|
||||
P3D_verify_contents _verify_contents;
|
||||
std::string _platform;
|
||||
std::string _log_directory;
|
||||
std::string _log_basename;
|
||||
std::string _log_pathname;
|
||||
std::string _temp_directory;
|
||||
bool _trusted_environment;
|
||||
bool _console_environment;
|
||||
int _plugin_major_version;
|
||||
int _plugin_minor_version;
|
||||
int _plugin_sequence_version;
|
||||
bool _plugin_official_version;
|
||||
std::string _plugin_distributor;
|
||||
std::string _coreapi_host_url;
|
||||
time_t _coreapi_timestamp;
|
||||
std::string _coreapi_set_ver;
|
||||
std::string _super_mirror_url;
|
||||
|
||||
typedef std::vector<std::string> SupportedPlatforms;
|
||||
SupportedPlatforms _supported_platforms;
|
||||
|
||||
P3D_object *_undefined_object;
|
||||
P3D_object *_none_object;
|
||||
P3D_object *_true_object;
|
||||
P3D_object *_false_object;
|
||||
|
||||
typedef std::set<std::string> ApprovedCerts;
|
||||
ApprovedCerts _approved_certs;
|
||||
|
||||
typedef std::set<P3DInstance *> Instances;
|
||||
Instances _instances;
|
||||
P3DAuthSession *_auth_session;
|
||||
|
||||
typedef std::map<std::string, P3DSession *> Sessions;
|
||||
Sessions _sessions;
|
||||
|
||||
typedef std::map<std::string, P3DHost *> Hosts;
|
||||
Hosts _hosts;
|
||||
|
||||
typedef std::set<std::string> TempFilenames;
|
||||
TempFilenames _temp_filenames;
|
||||
int _next_temp_filename_counter;
|
||||
|
||||
int _unique_id;
|
||||
|
||||
// This condition var is waited on the main thread and signaled in a sub-
|
||||
// thread when new request notices arrive.
|
||||
P3DConditionVar _request_ready;
|
||||
|
||||
// We may need a thread to send async request notices to callers.
|
||||
bool _notify_thread_continue;
|
||||
bool _started_notify_thread;
|
||||
THREAD _notify_thread;
|
||||
// This queue of instances that need to send notifications is protected by
|
||||
// _notify_ready's mutex.
|
||||
typedef std::vector<P3DInstance *> NotifyInstances;
|
||||
NotifyInstances _notify_instances;
|
||||
P3DConditionVar _notify_ready;
|
||||
|
||||
#ifndef _WIN32
|
||||
struct sigaction _old_sigpipe;
|
||||
#endif
|
||||
|
||||
static P3DInstanceManager *_global_ptr;
|
||||
};
|
||||
|
||||
#include "p3dInstanceManager.I"
|
||||
|
||||
#endif
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dIntObject.cxx
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#include "p3dIntObject.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DIntObject::
|
||||
P3DIntObject(int value) : _value(value) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DIntObject::
|
||||
P3DIntObject(const P3DIntObject ©) :
|
||||
P3DObject(copy),
|
||||
_value(copy._value)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fundamental type of this kind of object.
|
||||
*/
|
||||
P3D_object_type P3DIntObject::
|
||||
get_type() {
|
||||
return P3D_OT_int;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a boolean, if possible.
|
||||
*/
|
||||
bool P3DIntObject::
|
||||
get_bool() {
|
||||
return (_value != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to an integer, if possible.
|
||||
*/
|
||||
int P3DIntObject::
|
||||
get_int() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the indicated C++ string object with the value of this object coerced
|
||||
* to a string.
|
||||
*/
|
||||
void P3DIntObject::
|
||||
make_string(std::string &value) {
|
||||
std::ostringstream strm;
|
||||
strm << _value;
|
||||
value = strm.str();
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dIntObject.h
|
||||
* @author drose
|
||||
* @date 2009-06-30
|
||||
*/
|
||||
|
||||
#ifndef P3DINTOBJECT_H
|
||||
#define P3DINTOBJECT_H
|
||||
|
||||
#include "p3d_plugin_common.h"
|
||||
#include "p3dObject.h"
|
||||
|
||||
/**
|
||||
* An object type that contains an integer value.
|
||||
*/
|
||||
class P3DIntObject : public P3DObject {
|
||||
public:
|
||||
P3DIntObject(int value);
|
||||
P3DIntObject(const P3DIntObject ©);
|
||||
|
||||
public:
|
||||
virtual P3D_object_type get_type();
|
||||
virtual bool get_bool();
|
||||
virtual int get_int();
|
||||
virtual void make_string(std::string &value);
|
||||
|
||||
private:
|
||||
int _value;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,707 +0,0 @@
|
||||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file p3dMainObject.cxx
|
||||
* @author drose
|
||||
* @date 2009-07-10
|
||||
*/
|
||||
|
||||
#include "p3dMainObject.h"
|
||||
#include "p3dPythonObject.h"
|
||||
#include "p3dInstance.h"
|
||||
#include "p3dSession.h"
|
||||
#include "p3dStringObject.h"
|
||||
#include "p3dInstanceManager.h"
|
||||
|
||||
using std::ios;
|
||||
using std::max;
|
||||
using std::streamsize;
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DMainObject::
|
||||
P3DMainObject() :
|
||||
_pyobj(nullptr),
|
||||
_inst(nullptr),
|
||||
_unauth_play(false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
P3DMainObject::
|
||||
~P3DMainObject() {
|
||||
set_pyobj(nullptr);
|
||||
|
||||
// Clear the local properties.
|
||||
Properties::const_iterator pi;
|
||||
for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
|
||||
P3D_object *value = (*pi).second;
|
||||
P3D_OBJECT_DECREF(value);
|
||||
}
|
||||
_properties.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fundamental type of this kind of object.
|
||||
*/
|
||||
P3D_object_type P3DMainObject::
|
||||
get_type() {
|
||||
return P3D_OT_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a boolean, if possible.
|
||||
*/
|
||||
bool P3DMainObject::
|
||||
get_bool() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to an integer, if possible.
|
||||
*/
|
||||
int P3DMainObject::
|
||||
get_int() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object value coerced to a floating-point value, if possible.
|
||||
*/
|
||||
double P3DMainObject::
|
||||
get_float() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the indicated C++ string object with the value of this object coerced
|
||||
* to a string.
|
||||
*/
|
||||
void P3DMainObject::
|
||||
make_string(string &value) {
|
||||
if (_pyobj == nullptr) {
|
||||
value = "P3DMainObject";
|
||||
} else {
|
||||
int size = P3D_OBJECT_GET_STRING(_pyobj, nullptr, 0);
|
||||
char *buffer = new char[size];
|
||||
P3D_OBJECT_GET_STRING(_pyobj, buffer, size);
|
||||
value = string(buffer, size);
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the named property element in the object. The return value is a
|
||||
* new-reference P3D_object, or NULL on error.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
get_property(const string &property) {
|
||||
if (_pyobj == nullptr) {
|
||||
// Without a pyobj, we just report whatever's been stored locally.
|
||||
Properties::const_iterator pi;
|
||||
pi = _properties.find(property);
|
||||
if (pi != _properties.end()) {
|
||||
P3D_object *result = (*pi).second;
|
||||
P3D_OBJECT_INCREF(result);
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// With a pyobj, we pass the query down to it.
|
||||
return P3D_OBJECT_GET_PROPERTY(_pyobj, property.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies (or deletes, if value is NULL) the named property element in the
|
||||
* object. Returns true on success, false on failure.
|
||||
*/
|
||||
bool P3DMainObject::
|
||||
set_property(const string &property, bool needs_response, P3D_object *value) {
|
||||
// First, we set the property locally.
|
||||
if (value != nullptr) {
|
||||
Properties::iterator pi;
|
||||
pi = _properties.insert(Properties::value_type(property, nullptr)).first;
|
||||
assert(pi != _properties.end());
|
||||
P3D_object *orig_value = (*pi).second;
|
||||
if (orig_value != value) {
|
||||
P3D_OBJECT_XDECREF(orig_value);
|
||||
(*pi).second = value;
|
||||
P3D_OBJECT_INCREF(value);
|
||||
}
|
||||
} else {
|
||||
// (Or delete the property locally.)
|
||||
Properties::iterator pi;
|
||||
pi = _properties.find(property);
|
||||
if (pi != _properties.end()) {
|
||||
P3D_object *orig_value = (*pi).second;
|
||||
P3D_OBJECT_DECREF(orig_value);
|
||||
_properties.erase(pi);
|
||||
}
|
||||
}
|
||||
|
||||
if (_pyobj == nullptr) {
|
||||
// Without a pyobj, that's all we do.
|
||||
return true;
|
||||
}
|
||||
|
||||
// With a pyobj, we also pass this request down.
|
||||
return P3D_OBJECT_SET_PROPERTY(_pyobj, property.c_str(), needs_response, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the named method exists on this object, false otherwise.
|
||||
*/
|
||||
bool P3DMainObject::
|
||||
has_method(const string &method_name) {
|
||||
// Some special-case methods implemented in-place.
|
||||
if (method_name == "play") {
|
||||
return true;
|
||||
} else if (method_name == "read_game_log") {
|
||||
return true;
|
||||
} else if (method_name == "read_system_log") {
|
||||
return true;
|
||||
} else if (method_name == "read_log") {
|
||||
return true;
|
||||
} else if (method_name == "uninstall") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_pyobj == nullptr) {
|
||||
// No methods until we get our pyobj.
|
||||
return false;
|
||||
}
|
||||
|
||||
return P3D_OBJECT_HAS_METHOD(_pyobj, method_name.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the named method on the object, passing the indicated parameters.
|
||||
* If the method name is empty, invokes the object itself.
|
||||
*
|
||||
* If needs_response is true, the return value is a new-reference P3D_object
|
||||
* on success, or NULL on failure. If needs_response is false, the return
|
||||
* value is always NULL, and there is no way to determine success or failure.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
call(const string &method_name, bool needs_response,
|
||||
P3D_object *params[], int num_params) {
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
nout << "main." << method_name << "(";
|
||||
for (int i = 0; i < num_params; ++i) {
|
||||
if (i != 0) {
|
||||
nout << ", ";
|
||||
}
|
||||
int buffer_size = P3D_OBJECT_GET_REPR(params[i], nullptr, 0);
|
||||
char *buffer = new char[buffer_size];
|
||||
P3D_OBJECT_GET_REPR(params[i], buffer, buffer_size);
|
||||
nout.write(buffer, buffer_size);
|
||||
delete[] buffer;
|
||||
}
|
||||
nout << ")\n";
|
||||
|
||||
if (method_name == "play") {
|
||||
return call_play(params, num_params);
|
||||
} else if (method_name == "read_game_log") {
|
||||
return call_read_game_log(params, num_params);
|
||||
} else if (method_name == "read_system_log") {
|
||||
return call_read_system_log(params, num_params);
|
||||
} else if (method_name == "read_log") {
|
||||
return call_read_log(params, num_params);
|
||||
} else if (method_name == "uninstall") {
|
||||
return call_uninstall(params, num_params);
|
||||
}
|
||||
|
||||
if (_pyobj == nullptr) {
|
||||
// No methods until we get our pyobj.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return P3D_OBJECT_CALL(_pyobj, method_name.c_str(), needs_response,
|
||||
params, num_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a formatted representation of the value to the indicated string.
|
||||
* This is intended for developer assistance.
|
||||
*/
|
||||
void P3DMainObject::
|
||||
output(std::ostream &out) {
|
||||
out << "P3DMainObject";
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the internal pyobj pointer. This is the P3D_object that references
|
||||
* the actual PyObject held within the child process, corresponding to the
|
||||
* true main object there. The new object's reference count is incremented,
|
||||
* and the previous object's is decremented.
|
||||
*/
|
||||
void P3DMainObject::
|
||||
set_pyobj(P3D_object *pyobj) {
|
||||
if (pyobj == this) {
|
||||
// We are setting a reference directly to ourselves. This happens when
|
||||
// the application has accepted the main object we gave it in
|
||||
// set_instance_info(). This means the application is directly
|
||||
// manipulating this object as its appRunner.main. In this case, we don't
|
||||
// actually need to set the reference; instead, we clear anything we had
|
||||
// set.
|
||||
nout << "application shares main object\n";
|
||||
pyobj = nullptr;
|
||||
|
||||
} else if (pyobj != nullptr) {
|
||||
// In the alternate case, the application has its own, separate
|
||||
// appRunner.main object. Thus, we do need to set the pointer.
|
||||
nout << "application has its own main object\n";
|
||||
}
|
||||
|
||||
if (_pyobj != pyobj) {
|
||||
P3D_OBJECT_XDECREF(_pyobj);
|
||||
_pyobj = pyobj;
|
||||
if (_pyobj != nullptr) {
|
||||
P3D_OBJECT_INCREF(_pyobj);
|
||||
|
||||
// Now that we have a pyobj, we have to transfer down all of the
|
||||
// properties we'd set locally.
|
||||
apply_properties(_pyobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal pyobj pointer, or NULL if it has not yet been set.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
get_pyobj() const {
|
||||
return _pyobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the locally-set properties onto the indicated Python object, but
|
||||
* does not store the object. This is a one-time copy of the locally-set
|
||||
* properties (like "coreapiHostUrl" and the like) onto the indicated Python
|
||||
* object.
|
||||
*/
|
||||
void P3DMainObject::
|
||||
apply_properties(P3D_object *pyobj) {
|
||||
P3DPythonObject *p3dpyobj = nullptr;
|
||||
if (pyobj->_class == &P3DObject::_object_class) {
|
||||
p3dpyobj = ((P3DObject *)pyobj)->as_python_object();
|
||||
}
|
||||
|
||||
Properties::const_iterator pi;
|
||||
for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
|
||||
const string &property_name = (*pi).first;
|
||||
P3D_object *value = (*pi).second;
|
||||
if (p3dpyobj != nullptr && P3D_OBJECT_GET_TYPE(value) != P3D_OT_object) {
|
||||
// If we know we have an actual P3DPythonObject (we really expect this),
|
||||
// then we can call set_property_insecure() directly, because we want to
|
||||
// allow setting the initial properties even if Javascript has no
|
||||
// permissions to write into Python. But we don't allow setting objects
|
||||
// this way in any event.
|
||||
p3dpyobj->set_property_insecure(property_name, false, value);
|
||||
} else {
|
||||
// Otherwise, we go through the generic interface.
|
||||
P3D_OBJECT_SET_PROPERTY(pyobj, property_name.c_str(), false, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback pointer to the instance that owns this object. When this
|
||||
* instance destructs, it clears this pointer to NULL.
|
||||
*/
|
||||
void P3DMainObject::
|
||||
set_instance(P3DInstance *inst) {
|
||||
if (_inst != nullptr) {
|
||||
// Save the game log filename of the instance just before it goes away, in
|
||||
// case JavaScript asks for it later.
|
||||
_game_log_pathname = _inst->get_log_pathname();
|
||||
}
|
||||
|
||||
_inst = inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process remotely, as if the play button had been clicked. If
|
||||
* the application has not yet been validated, this pops up the validation
|
||||
* dialog.
|
||||
*
|
||||
* Only applicable if the application was in the ready state, or the unauth
|
||||
* state. Returns true if the application is now started, false otherwise.
|
||||
*
|
||||
* This may be invoked from the unauth state only once. If the user chooses
|
||||
* not to authorize the plugin at that time, it may not be invoked
|
||||
* automatically again.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
call_play(P3D_object *params[], int num_params) {
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
if (_inst == nullptr) {
|
||||
return inst_mgr->new_bool_object(false);
|
||||
}
|
||||
|
||||
// I guess there's no harm in allowing JavaScript to call play(), with or
|
||||
// without explicit scripting authorization.
|
||||
nout << "play() called from JavaScript\n";
|
||||
|
||||
if (!_inst->is_trusted()) {
|
||||
// Requires authorization. We allow this only once; beyond that, and
|
||||
// you're only annoying the user.
|
||||
if (!_unauth_play) {
|
||||
_unauth_play = true;
|
||||
_inst->splash_button_clicked_main_thread();
|
||||
}
|
||||
|
||||
} else if (!_inst->is_started()) {
|
||||
// We allow calling play() from a ready state without limit, but probably
|
||||
// only once will be necessary.
|
||||
_inst->splash_button_clicked_main_thread();
|
||||
}
|
||||
|
||||
return inst_mgr->new_bool_object(_inst->is_started());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire logfile as a string, and returns it to the calling
|
||||
* JavaScript process.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
call_read_game_log(P3D_object *params[], int num_params) {
|
||||
if (_inst != nullptr) {
|
||||
string log_pathname = _inst->get_log_pathname();
|
||||
return read_log(log_pathname, params, num_params);
|
||||
}
|
||||
|
||||
if (!_game_log_pathname.empty()) {
|
||||
// The instance has already finished, but we saved its log filename.
|
||||
return read_log(_game_log_pathname, params, num_params);
|
||||
}
|
||||
|
||||
// No log available for us.
|
||||
nout << "read_game_log: error: game log name unknown" << "\n";
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
return inst_mgr->new_undefined_object();
|
||||
}
|
||||
|
||||
/**
|
||||
* As above, but reads the system log, the logfile for the installation
|
||||
* process.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
call_read_system_log(P3D_object *params[], int num_params) {
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
string log_pathname = inst_mgr->get_log_pathname();
|
||||
return read_log(log_pathname, params, num_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a named logfile. The filename must end in ".log" and must not
|
||||
* contain any slashes or colons (it must be found within the log directory).
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
call_read_log(P3D_object *params[], int num_params) {
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
if (num_params < 1) {
|
||||
nout << "read_log: error: not enough parameters" << "\n";
|
||||
return inst_mgr->new_undefined_object();
|
||||
}
|
||||
|
||||
int size = P3D_OBJECT_GET_STRING(params[0], nullptr, 0);
|
||||
char *buffer = new char[size];
|
||||
P3D_OBJECT_GET_STRING(params[0], buffer, size);
|
||||
string log_filename = string(buffer, size);
|
||||
delete[] buffer;
|
||||
|
||||
if (log_filename.size() < 4 || log_filename.substr(log_filename.size() - 4) != string(".log")) {
|
||||
// Wrong filename extension.
|
||||
nout << "read_log: error: invalid filename" << "\n";
|
||||
return inst_mgr->new_undefined_object();
|
||||
}
|
||||
|
||||
size_t slash = log_filename.find('/');
|
||||
if (slash != string::npos) {
|
||||
// No slashes allowed.
|
||||
nout << "read_log: error: invalid filename" << "\n";
|
||||
return inst_mgr->new_undefined_object();
|
||||
}
|
||||
|
||||
slash = log_filename.find('\\');
|
||||
if (slash != string::npos) {
|
||||
// Nor backslashes.
|
||||
nout << "read_log: error: invalid filename" << "\n";
|
||||
return inst_mgr->new_undefined_object();
|
||||
}
|
||||
|
||||
size_t colon = log_filename.find(':');
|
||||
if (colon != string::npos) {
|
||||
// Nor colons, for that matter.
|
||||
nout << "read_log: error: invalid filename" << "\n";
|
||||
return inst_mgr->new_undefined_object();
|
||||
}
|
||||
|
||||
string log_pathname = inst_mgr->get_log_directory() + log_filename;
|
||||
P3D_object *result = read_log(log_pathname, params + 1, num_params - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* log-reader meta function that handles reading previous log files in
|
||||
* addition to the present one
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
read_log(const string &log_pathname, P3D_object *params[], int num_params) {
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
string log_directory = inst_mgr->get_log_directory();
|
||||
std::ostringstream log_data;
|
||||
|
||||
// Check the first parameter, if any--if given, it specifies the last n
|
||||
// bytes to retrieve.
|
||||
size_t tail_bytes = 0;
|
||||
if (num_params > 0) {
|
||||
tail_bytes = (size_t)max(P3D_OBJECT_GET_INT(params[0]), 0);
|
||||
}
|
||||
// Check the second parameter, if any--if given, it specifies the first n
|
||||
// bytes to retrieve.
|
||||
size_t head_bytes = 0;
|
||||
if (num_params > 1) {
|
||||
head_bytes = (size_t)max(P3D_OBJECT_GET_INT(params[1]), 0);
|
||||
}
|
||||
// Check the third parameter, if any--if given, it specifies the last n
|
||||
// bytes to retrieve from previous copies of this file.
|
||||
size_t tail_bytes_prev = 0;
|
||||
if (num_params > 2) {
|
||||
tail_bytes_prev = (size_t)max(P3D_OBJECT_GET_INT(params[2]), 0);
|
||||
}
|
||||
// Check the fourth parameter, if any--if given, it specifies the first n
|
||||
// bytes to retrieve from previous copies of this file.
|
||||
size_t head_bytes_prev = 0;
|
||||
if (num_params > 3) {
|
||||
head_bytes_prev = (size_t)max(P3D_OBJECT_GET_INT(params[3]), 0);
|
||||
}
|
||||
|
||||
// Determine the base of the log file names
|
||||
nout << "log_pathname: " << log_pathname << "\n";
|
||||
string log_basename = log_pathname;
|
||||
size_t slash = log_basename.rfind('/');
|
||||
if (slash != string::npos) {
|
||||
log_basename = log_basename.substr(slash + 1);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
slash = log_basename.rfind('\\');
|
||||
if (slash != string::npos) {
|
||||
log_basename = log_basename.substr(slash + 1);
|
||||
}
|
||||
#endif // _WIN32
|
||||
string log_leafname_primary = log_basename;
|
||||
int dash = log_basename.rfind("-");
|
||||
if (dash != string::npos) {
|
||||
log_basename = log_basename.substr(0, dash+1);
|
||||
} else {
|
||||
int dotLog = log_basename.rfind(".log");
|
||||
if (dotLog != string::npos) {
|
||||
log_basename = log_basename.substr(0, dotLog);
|
||||
log_basename += "-";
|
||||
}
|
||||
}
|
||||
|
||||
// Read matching files
|
||||
std::vector<string> all_logs;
|
||||
int log_matches_found = 0;
|
||||
string log_matching_pathname;
|
||||
inst_mgr->scan_directory(log_directory, all_logs);
|
||||
for (int i = (int)all_logs.size() - 1; i >= 0; --i) {
|
||||
if (all_logs[i] == log_leafname_primary ||
|
||||
(all_logs[i].find(log_basename) == 0 &&
|
||||
all_logs[i].size() > 4 &&
|
||||
all_logs[i].substr(all_logs[i].size() - 4) == string(".log"))) {
|
||||
log_matches_found++;
|
||||
log_matching_pathname = (log_directory + all_logs[i]);
|
||||
read_log_file(log_matching_pathname, tail_bytes, head_bytes, log_data);
|
||||
tail_bytes = tail_bytes_prev;
|
||||
head_bytes = head_bytes_prev;
|
||||
}
|
||||
}
|
||||
|
||||
if (log_matches_found == 0) {
|
||||
nout << "read_log: warning: no matching file(s) on disk." << "\n";
|
||||
}
|
||||
|
||||
string log_data_str = log_data.str();
|
||||
P3D_object *result = new P3DStringObject(log_data_str);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The generic log-reader function.
|
||||
*/
|
||||
void P3DMainObject::
|
||||
read_log_file(const string &log_pathname,
|
||||
size_t tail_bytes, size_t head_bytes,
|
||||
std::ostringstream &log_data) {
|
||||
|
||||
// Get leaf name
|
||||
string log_leafname = log_pathname;
|
||||
size_t slash = log_leafname.rfind('/');
|
||||
if (slash != string::npos) {
|
||||
log_leafname = log_leafname.substr(slash + 1);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
slash = log_leafname.rfind('\\');
|
||||
if (slash != string::npos) {
|
||||
log_leafname = log_leafname.substr(slash + 1);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// Render log file header to log_data
|
||||
log_data << "=======================================";
|
||||
log_data << "=======================================" << "\n";
|
||||
log_data << "== PandaLog-" << log_pathname << "\n";
|
||||
|
||||
// load file
|
||||
ifstream log(log_pathname.c_str(), ios::in);
|
||||
if (!log) {
|
||||
log_data << "== PandaLog-" << "Error opening file";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the size of the file.
|
||||
log.seekg(0, ios::end);
|
||||
size_t file_size = (size_t)log.tellg();
|
||||
nout << "read_log: " << log_pathname << " is " << file_size
|
||||
<< " bytes, tail_bytes = " << tail_bytes << ", head_bytes = "
|
||||
<< head_bytes << "\n";
|
||||
|
||||
// Early out if the file is empty
|
||||
if (file_size == (size_t)0) {
|
||||
log_data << "== PandaLog-" << "Empty File";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are getting the full file
|
||||
size_t full_bytes = 0;
|
||||
if (file_size <= head_bytes + tail_bytes) {
|
||||
// We will return the entire log.
|
||||
full_bytes = file_size;
|
||||
head_bytes = 0;
|
||||
tail_bytes = 0;
|
||||
}
|
||||
|
||||
// Allocate a temp buffer to hold file data
|
||||
size_t buffer_bytes = max(max(full_bytes, head_bytes), tail_bytes) + 1;
|
||||
nout << "allocating " << buffer_bytes << " bytes to read at a time from file of size " << file_size << ".\n";
|
||||
char *buffer = new char[buffer_bytes];
|
||||
if (buffer == nullptr) {
|
||||
log_data << "== PandaLog-" << "Error allocating buffer";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Render log data if full file is to be fetched
|
||||
if (full_bytes > 0) {
|
||||
log.seekg(0, ios::beg);
|
||||
log.read(buffer, full_bytes);
|
||||
streamsize read_bytes = log.gcount();
|
||||
assert(read_bytes < (streamsize)buffer_bytes);
|
||||
buffer[read_bytes] = '\0';
|
||||
log_data << "== PandaLog-" << "Full Start";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
log_data << buffer;
|
||||
log_data << "== PandaLog-" << "Full End";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
}
|
||||
|
||||
// Render log data if head bytes are to be fetched
|
||||
if (head_bytes > 0) {
|
||||
log.seekg(0, ios::beg);
|
||||
log.read(buffer, head_bytes);
|
||||
streamsize read_bytes = log.gcount();
|
||||
assert(read_bytes < (streamsize)buffer_bytes);
|
||||
buffer[read_bytes] = '\0';
|
||||
log_data << "== PandaLog-" << "Head Start";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
log_data << buffer << "\n";
|
||||
log_data << "== PandaLog-" << "Head End";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
}
|
||||
|
||||
// Render separator if head & tail bytes are to be fetched
|
||||
if ((head_bytes > 0) && (tail_bytes > 0)) {
|
||||
log_data << "== PandaLog-" << "truncated";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
}
|
||||
|
||||
// Render log data if tail bytes are to be fetched
|
||||
if (tail_bytes > 0) {
|
||||
log.seekg(file_size - tail_bytes, ios::beg);
|
||||
log.read(buffer, tail_bytes);
|
||||
streamsize read_bytes = log.gcount();
|
||||
assert(read_bytes < (streamsize)buffer_bytes);
|
||||
buffer[read_bytes] = '\0';
|
||||
log_data << "== PandaLog-" << "Tail Start";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
log_data << buffer;
|
||||
log_data << "== PandaLog-" << "Tail End";
|
||||
log_data << " " << "(" << log_leafname << ")" << "\n";
|
||||
}
|
||||
|
||||
// Render log file footer to log_data log_data <<
|
||||
// "======================================="; log_data <<
|
||||
// "=======================================" << "\n";
|
||||
|
||||
// cleanup
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the uninstall() plugin method, which removes all Panda installed
|
||||
* files for a particular host, or referenced by a particular p3d file.
|
||||
*/
|
||||
P3D_object *P3DMainObject::
|
||||
call_uninstall(P3D_object *params[], int num_params) {
|
||||
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
||||
|
||||
// Get the first parameter, the uninstall mode.
|
||||
string mode;
|
||||
if (num_params > 0) {
|
||||
int size = P3D_OBJECT_GET_STRING(params[0], nullptr, 0);
|
||||
char *buffer = new char[size];
|
||||
P3D_OBJECT_GET_STRING(params[0], buffer, size);
|
||||
mode = string(buffer, size);
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
if (mode == "all") {
|
||||
nout << "uninstall all\n";
|
||||
inst_mgr->uninstall_all();
|
||||
return inst_mgr->new_bool_object(true);
|
||||
}
|
||||
|
||||
if (_inst != nullptr) {
|
||||
nout << "uninstall " << mode << " for " << _inst << "\n";
|
||||
bool success = false;
|
||||
if (mode == "host") {
|
||||
success = _inst->uninstall_host();
|
||||
} else {
|
||||
success = _inst->uninstall_packages();
|
||||
}
|
||||
return inst_mgr->new_bool_object(success);
|
||||
}
|
||||
|
||||
nout << "couldn't uninstall; no instance.\n";
|
||||
return inst_mgr->new_bool_object(false);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user