open_toontown_panda3d/direct/src/plugin_standalone/panda3d.cxx

943 lines
29 KiB
C++

/**
* 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 panda3d.cxx
* @author drose
* @date 2009-06-03
*/
#include "panda3d.h"
#include "load_plugin.h"
#include "p3d_plugin_config.h"
#include "find_root_dir.h"
#include "pandaSystem.h"
#include "dtool_platform.h"
#include "pandaVersion.h"
#include "panda_getopt.h"
#include <ctype.h>
#include <sstream>
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#endif
using std::cerr;
using std::cout;
using std::string;
/**
*
*/
Panda3D::
Panda3D(bool console_environment) : Panda3DBase(console_environment) {
// We use the runtime PandaSystem setting for this value, rather than the
// hard-compiled-in setting, to allow users to override this with a
// Config.prc variable if needed.
_host_url = PandaSystem::get_package_host_url();
}
/**
* Starts the program going, with command-line arguments. Returns 0 on
* success, nonzero on failure.
*/
int Panda3D::
run_command_line(int argc, char *argv[]) {
extern char *optarg;
extern int optind;
// We prefix a "+" sign to tell getopt not to parse options following the
// first not-option parameter. (These will be passed into the sub-process.)
const char *optstr = "+mu:M:Sp:nfw:t:s:o:l:iVUPh";
bool allow_multiple = false;
int flag = getopt(argc, argv, optstr);
while (flag != EOF) {
switch (flag) {
case 'm':
allow_multiple = true;
break;
case 'u':
_host_url = optarg;
break;
case 'M':
_super_mirror_url = optarg;
break;
case 'S':
_enable_security = true;
break;
case 'p':
_this_platform = optarg;
_coreapi_platform = optarg;
break;
case 'n':
_verify_contents = P3D_VC_normal;
break;
case 'f':
_verify_contents = P3D_VC_force;
break;
case 'w':
if (strcmp(optarg, "toplevel") == 0) {
_window_type = P3D_WT_toplevel;
} else if (strcmp(optarg, "embedded") == 0) {
_window_type = P3D_WT_embedded;
} else if (strcmp(optarg, "fullscreen") == 0) {
_window_type = P3D_WT_fullscreen;
} else if (strcmp(optarg, "hidden") == 0) {
_window_type = P3D_WT_hidden;
} else {
cerr << "Invalid value for -w: " << optarg << "\n";
return 1;
}
break;
case 't':
if (!parse_token(optarg)) {
cerr << "Web tokens (-t) must be of the form token=value: " << optarg << "\n";
return 1;
}
break;
case 's':
if (!parse_int_pair(optarg, _win_width, _win_height)) {
cerr << "Invalid value for -s: " << optarg << "\n";
return 1;
}
_got_win_size = true;
break;
case 'o':
if (!parse_int_pair(optarg, _win_x, _win_y)) {
cerr << "Invalid value for -o: " << optarg << "\n";
return 1;
}
break;
case 'l':
_log_dirname = Filename::from_os_specific(optarg).to_os_specific();
_log_basename = "p3dcore";
break;
case 'i':
{
P3D_token token;
token._keyword = "keep_pythonpath";
token._value = "1";
_tokens.push_back(token);
token._keyword = "interactive_console";
token._value = "1";
_tokens.push_back(token);
// We should also ignore control-C in this case, so that an interrupt
// will be delivered to the subordinate Python process and return to a
// command shell, and won't just kill the panda3d process.
#ifdef _WIN32
SetConsoleCtrlHandler(nullptr, true);
#else
struct sigaction ignore;
memset(&ignore, 0, sizeof(ignore));
ignore.sa_handler = SIG_IGN;
sigaction(SIGINT, &ignore, nullptr);
#endif // _WIN32
}
break;
case 'V':
cout << P3D_PLUGIN_MAJOR_VERSION << "."
<< P3D_PLUGIN_MINOR_VERSION << "."
<< P3D_PLUGIN_SEQUENCE_VERSION;
#ifndef PANDA_OFFICIAL_VERSION
cout << "c";
#endif
cout << "\n";
exit(0);
case 'U':
cout << _host_url << "\n";
exit(0);
case 'P':
cout << DTOOL_PLATFORM << "\n";
exit(0);
case 'h':
case '?':
case '+':
default:
usage();
return 1;
}
flag = getopt(argc, argv, optstr);
}
argc -= (optind-1);
argv += (optind-1);
if (argc < 2 && _exit_with_last_instance) {
// No instances on the command line--that *might* be an error.
usage();
return 1;
}
if (!post_arg_processing()) {
return 1;
}
int num_instance_filenames, num_instance_args;
char **instance_filenames, **instance_args;
if (argc > 1) {
if (allow_multiple) {
// With -m, the remaining arguments are all instance filenames.
num_instance_filenames = argc - 1;
instance_filenames = argv + 1;
num_instance_args = 0;
instance_args = argv + argc;
} else {
// Without -m, there is one instance filename, and everything else gets
// delivered to that instance.
num_instance_filenames = 1;
instance_filenames = argv + 1;
num_instance_args = argc - 2;
instance_args = argv + 2;
}
if (_window_type == P3D_WT_embedded) {
// The user asked for an embedded window. Create a toplevel window to
// be its parent, of the requested size.
if (_win_width == 0 && _win_height == 0) {
_win_width = 800;
_win_height = 600;
}
make_parent_window();
// Center the child window(s) within the parent window.
#ifdef _WIN32
assert(_parent_window._window_handle_type == P3D_WHT_win_hwnd);
HWND parent_hwnd = _parent_window._handle._win_hwnd._hwnd;
RECT rect;
GetClientRect(parent_hwnd, &rect);
_win_x = (int)(rect.right * 0.1);
_win_y = (int)(rect.bottom * 0.1);
_win_width = (int)(rect.right * 0.8);
_win_height = (int)(rect.bottom * 0.8);
#endif
// Subdivide the window into num_x_spans * num_y_spans sub-windows.
int num_y_spans = int(sqrt((double)num_instance_filenames));
int num_x_spans = (num_instance_filenames + num_y_spans - 1) / num_y_spans;
int origin_x = _win_x;
int origin_y = _win_y;
_win_width = _win_width / num_x_spans;
_win_height = _win_height / num_y_spans;
_got_win_size = true;
for (int yi = 0; yi < num_y_spans; ++yi) {
for (int xi = 0; xi < num_x_spans; ++xi) {
int i = yi * num_x_spans + xi;
if (i >= num_instance_filenames) {
continue;
}
// Create instance i at window slot (xi, yi).
_win_x = origin_x + xi * _win_width;
_win_y = origin_y + yi * _win_height;
P3D_instance *inst = create_instance
(instance_filenames[i], true,
instance_args, num_instance_args);
_instances.insert(inst);
}
}
} else {
// Not an embedded window. Create each window with the same parameters.
for (int i = 0; i < num_instance_filenames; ++i) {
P3D_instance *inst = create_instance
(instance_filenames[i], true,
instance_args, num_instance_args);
_instances.insert(inst);
}
}
}
run_main_loop();
// All instances have finished; we can exit.
unload_plugin(cerr);
return 0;
}
/**
* Sets up some internal state after processing the command-line arguments.
* Returns true on success, false on failure.
*/
bool Panda3D::
post_arg_processing() {
// Now is a good time to assign _root_dir.
_root_dir = find_root_dir();
// Set host_url_prefix to end with a slash.
_host_url_prefix = _host_url;
if (!_host_url_prefix.empty() && _host_url_prefix[_host_url_prefix.length() - 1] != '/') {
_host_url_prefix += '/';
}
_download_url_prefix = _host_url_prefix;
// If the "super mirror" URL is a filename, convert it to a file: url.
if (!_super_mirror_url.empty()) {
if (!is_url(_super_mirror_url)) {
Filename filename = Filename::from_os_specific(_super_mirror_url);
filename.make_absolute();
string path = filename.to_os_generic();
if (!path.empty() && path[0] != '/') {
// On Windows, a leading drive letter must be preceded by an
// additional slash.
path = "/" + path;
}
_super_mirror_url = "file://" + path;
}
// And make sure the super_mirror_url_prefix ends with a slash.
_super_mirror_url_prefix = _super_mirror_url;
if (!_super_mirror_url_prefix.empty() && _super_mirror_url_prefix[_super_mirror_url_prefix.length() - 1] != '/') {
_super_mirror_url_prefix += '/';
}
}
if (!get_plugin()) {
cerr << "Unable to load Panda3D plugin.\n";
return false;
}
// Set up the "super mirror" URL, if specified.
if (!_super_mirror_url.empty()) {
P3D_set_super_mirror_ptr(_super_mirror_url.c_str());
}
return true;
}
/**
* Downloads the contents.xml file from the named URL and attempts to use it
* to load the core API. Returns true on success, false on failure.
*/
bool Panda3D::
get_plugin() {
// First, look for the existing contents.xml file.
bool success = false;
bool is_fresh = false;
Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
if (_verify_contents != P3D_VC_force) {
if (read_contents_file(contents_filename, false)) {
if (_verify_contents == P3D_VC_none || time(nullptr) < _contents_expiration) {
// Got the file, and it's good.
success = true;
}
}
}
if (!success) {
// Couldn't read it (or it wasn't current enough), so go get a new one.
if (!download_contents_file(contents_filename)) {
// We don't have a usable contents.xml file.
return false;
}
is_fresh = true;
}
// Now that we've downloaded the contents file successfully, start the Core
// API.
if (!get_core_api()) {
// We failed. Make sure contents.xml is up-to-date and try again.
if (!is_fresh && download_contents_file(contents_filename) && get_core_api()) {
return true;
} else {
return false;
}
}
return true;
}
/**
* Redownloads the contents.xml file from the named URL without first checking
* if it is up to date. Returns true if we have a contents.xml file that
* might be usable, false otherwise.
*/
bool Panda3D::
download_contents_file(const Filename &contents_filename) {
bool success = false;
HTTPClient *http = HTTPClient::get_global_ptr();
// Try the super_mirror first.
if (!_super_mirror_url_prefix.empty()) {
// We don't bother putting a uniquifying query string when we're
// downloading this file from the super_mirror. The super_mirror is by
// definition a cache, so it doesn't make sense to bust caches here.
string url = _super_mirror_url_prefix + "contents.xml";
PT(HTTPChannel) channel = http->make_channel(false);
channel->get_document(url);
Filename tempfile = Filename::temporary("", "p3d_");
if (!channel->download_to_file(tempfile)) {
cerr << "Unable to download " << url << "\n";
tempfile.unlink();
} else {
// Successfully downloaded from the super_mirror; try to read it.
success = read_contents_file(tempfile, true);
tempfile.unlink();
}
}
if (!success) {
// Go download contents.xml from the actual host.
std::ostringstream strm;
strm << _host_url_prefix << "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.
strm << "?" << time(nullptr);
string url = strm.str();
// We might as well explicitly request the cache to be disabled too, since
// we have an interface for that via HTTPChannel.
DocumentSpec request(url);
request.set_cache_control(DocumentSpec::CC_no_cache);
PT(HTTPChannel) channel = http->make_channel(false);
channel->get_document(request);
// Since we have to download some of it, might as well ask the core API to
// check all of it.
if (_verify_contents == P3D_VC_none) {
_verify_contents = P3D_VC_normal;
}
// First, download it to a temporary file.
Filename tempfile = Filename::temporary("", "p3d_");
if (!channel->download_to_file(tempfile)) {
cerr << "Unable to download " << url << "\n";
// Couldn't download, but try to read the existing contents.xml file
// anyway. Maybe it's good enough.
success = read_contents_file(contents_filename, false);
} else {
// Successfully downloaded; read it and move it into place.
success = read_contents_file(tempfile, true);
}
tempfile.unlink();
}
return success;
}
/**
* Attempts to open and read the contents.xml file on disk. Copies the file
* to its standard location on success. Returns true on success, false on
* failure.
*/
bool Panda3D::
read_contents_file(const Filename &contents_filename, bool fresh_download) {
string os_contents_filename = contents_filename.to_os_specific();
TiXmlDocument doc(os_contents_filename.c_str());
if (!doc.LoadFile()) {
return false;
}
bool found_core_package = false;
TiXmlElement *xcontents = doc.FirstChildElement("contents");
if (xcontents != nullptr) {
int max_age = P3D_CONTENTS_DEFAULT_MAX_AGE;
xcontents->Attribute("max_age", &max_age);
// Get the latest possible expiration time, based on the max_age
// indication. Any expiration time later than this is in error.
time_t now = time(nullptr);
_contents_expiration = now + (time_t)max_age;
if (fresh_download) {
// Update the XML with the new download information.
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
while (xorig != nullptr) {
xcontents->RemoveChild(xorig);
xorig = xcontents->FirstChildElement("orig");
}
xorig = new TiXmlElement("orig");
xcontents->LinkEndChild(xorig);
xorig->SetAttribute("expiration", (int)_contents_expiration);
} else {
// Read the expiration time from the XML.
int expiration = 0;
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
if (xorig != nullptr) {
xorig->Attribute("expiration", &expiration);
}
_contents_expiration = std::min(_contents_expiration, (time_t)expiration);
}
// Look for the <host> entry; it might point us at a different download
// URL, and it might mention some mirrors.
find_host(xcontents);
// Now look for the core API package.
_coreapi_set_ver = "";
TiXmlElement *xpackage = xcontents->FirstChildElement("package");
while (xpackage != nullptr) {
const char *name = xpackage->Attribute("name");
if (name != nullptr && strcmp(name, "coreapi") == 0) {
const char *platform = xpackage->Attribute("platform");
if (platform != nullptr && _coreapi_platform == string(platform)) {
_coreapi_dll.load_xml(xpackage);
const char *set_ver = xpackage->Attribute("set_ver");
if (set_ver != nullptr) {
_coreapi_set_ver = set_ver;
}
found_core_package = true;
break;
}
}
xpackage = xpackage->NextSiblingElement("package");
}
}
if (!found_core_package) {
// Couldn't find the coreapi package description.
nout << "No coreapi package defined in contents file for "
<< _coreapi_platform << "\n";
return false;
}
// Check the coreapi_set_ver token. If it is given, it specifies a minimum
// Core API version number we expect to find. If we didn't find that
// number, perhaps our contents.xml is out of date.
string coreapi_set_ver = lookup_token("coreapi_set_ver");
if (!coreapi_set_ver.empty()) {
nout << "Instance asked for Core API set_ver " << coreapi_set_ver
<< ", we found " << _coreapi_set_ver << "\n";
// But don't bother if we just freshly downloaded it.
if (!fresh_download) {
if (compare_seq(coreapi_set_ver, _coreapi_set_ver) > 0) {
// The requested set_ver value is higher than the one we have on file;
// our contents.xml file must be out of date after all.
nout << "expiring contents.xml\n";
_contents_expiration = 0;
}
}
}
// Success. Now copy the file into place.
Filename standard_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
if (fresh_download) {
Filename tempfile = Filename::temporary("", "p3d_");
string os_specific = tempfile.to_os_specific();
if (!doc.SaveFile(os_specific.c_str())) {
nout << "Couldn't write to " << tempfile << "\n";
tempfile.unlink();
return false;
}
tempfile.rename_to(standard_filename);
} else {
if (contents_filename != standard_filename) {
if (!contents_filename.rename_to(standard_filename)) {
nout << "Couldn't move contents.xml to " << standard_filename << "\n";
contents_filename.unlink();
return false;
}
}
}
return true;
}
/**
* Scans the <contents> element for the matching <host> element.
*/
void Panda3D::
find_host(TiXmlElement *xcontents) {
TiXmlElement *xhost = xcontents->FirstChildElement("host");
if (xhost != nullptr) {
const char *url = xhost->Attribute("url");
if (url != nullptr && _host_url == string(url)) {
// We're the primary host. This is the normal case.
read_xhost(xhost);
return;
} else {
// We're not the primary host; perhaps we're an alternate host.
TiXmlElement *xalthost = xhost->FirstChildElement("alt_host");
while (xalthost != nullptr) {
const char *url = xalthost->Attribute("url");
if (url != nullptr && _host_url == string(url)) {
// Yep, we're this alternate host.
read_xhost(xhost);
return;
}
xalthost = xalthost->NextSiblingElement("alt_host");
}
}
// Hmm, didn't find the URL we used mentioned. Assume we're the primary
// host.
read_xhost(xhost);
}
}
/**
* Reads the host data from the <host> (or <alt_host>) entry in the
* contents.xml file.
*/
void Panda3D::
read_xhost(TiXmlElement *xhost) {
// Get the "download" URL, which is the source from which we download
// everything other than the contents.xml file.
const char *download_url = xhost->Attribute("download_url");
if (download_url == nullptr) {
download_url = xhost->Attribute("url");
}
if (download_url != nullptr) {
_download_url_prefix = download_url;
} else {
_download_url_prefix = _host_url_prefix;
}
if (!_download_url_prefix.empty()) {
if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') {
_download_url_prefix += "/";
}
}
TiXmlElement *xmirror = xhost->FirstChildElement("mirror");
while (xmirror != nullptr) {
const char *url = xmirror->Attribute("url");
if (url != nullptr) {
add_mirror(url);
}
xmirror = xmirror->NextSiblingElement("mirror");
}
}
/**
* Adds a new URL to serve as a mirror for this host. The mirrors will be
* consulted first, before consulting the host directly.
*/
void Panda3D::
add_mirror(string mirror_url) {
// Ensure the URL ends in a slash.
if (!mirror_url.empty() && mirror_url[mirror_url.size() - 1] != '/') {
mirror_url += '/';
}
// Add it to the _mirrors list, but only if it's not already there.
if (find(_mirrors.begin(), _mirrors.end(), mirror_url) == _mirrors.end()) {
_mirrors.push_back(mirror_url);
}
}
/**
* Selects num_mirrors elements, chosen at random, from the _mirrors list.
* Adds the selected mirrors to result. If there are fewer than num_mirrors
* elements in the list, adds only as many mirrors as we can get.
*/
void Panda3D::
choose_random_mirrors(vector_string &result, int num_mirrors) {
pvector<size_t> selected;
size_t num_to_select = std::min(_mirrors.size(), (size_t)num_mirrors);
while (num_to_select > 0) {
size_t i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
while (find(selected.begin(), selected.end(), i) != selected.end()) {
// Already found this i, find a new one.
i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
}
selected.push_back(i);
result.push_back(_mirrors[i]);
--num_to_select;
}
}
/**
* Checks the core API DLL file against the specification in the contents
* file, and downloads it if necessary.
*/
bool Panda3D::
get_core_api() {
bool is_fresh = false;
if (!_coreapi_dll.quick_verify(_root_dir)) {
if (!download_core_api()) {
return false;
}
is_fresh = true;
}
// Now we've got the DLL. Load it.
string pathname = _coreapi_dll.get_pathname(_root_dir);
#ifdef P3D_PLUGIN_P3D_PLUGIN
// This is a convenience macro for development. If defined and nonempty, it
// indicates the name of the plugin DLL that we will actually run, even
// after downloading a possibly different (presumably older) version. Its
// purpose is to simplify iteration on the plugin DLL.
string override_filename = P3D_PLUGIN_P3D_PLUGIN;
if (!override_filename.empty()) {
pathname = override_filename;
}
#endif // P3D_PLUGIN_P3D_PLUGIN
bool trusted_environment = !_enable_security;
Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
if (!load_plugin(pathname, contents_filename.to_os_specific(),
_host_url, _verify_contents, _this_platform, _log_dirname,
_log_basename, trusted_environment, _console_environment,
_root_dir, _host_dir, _start_dir, cerr)) {
// If we're not sure this is the latest version, make sure it is up-to-
// date, and then try again.
if (is_fresh || !download_core_api() ||
!load_plugin(pathname, contents_filename.to_os_specific(),
_host_url, _verify_contents, _this_platform, _log_dirname,
_log_basename, trusted_environment, _console_environment,
_root_dir, _host_dir, _start_dir, cerr)) {
cerr << "Unable to launch core API in " << pathname << "\n";
return false;
}
}
// Successfully loaded.
#ifdef PANDA_OFFICIAL_VERSION
static const bool official = true;
#else
static const bool official = false;
#endif
// Format the coreapi_timestamp as a string, for passing as a parameter.
std::ostringstream stream;
stream << _coreapi_dll.get_timestamp();
string coreapi_timestamp = stream.str();
P3D_set_plugin_version_ptr(P3D_PLUGIN_MAJOR_VERSION, P3D_PLUGIN_MINOR_VERSION,
P3D_PLUGIN_SEQUENCE_VERSION, official,
PANDA_DISTRIBUTOR,
_host_url.c_str(), coreapi_timestamp.c_str(),
_coreapi_set_ver.c_str());
return true;
}
/**
* Downloads the latest version of the core API from the plug-in server.
*/
bool Panda3D::
download_core_api() {
// The DLL file needs to be downloaded. Build up our list of URL's to
// attempt to download it from, in reverse order.
string url;
vector_string core_urls;
// Our last act of desperation: hit the original host, with a query
// uniquifier, to break through any caches.
std::ostringstream strm;
strm << _download_url_prefix << _coreapi_dll.get_filename()
<< "?" << time(nullptr);
url = strm.str();
core_urls.push_back(url);
// Before we try that, we'll hit the original host, without a uniquifier.
url = _download_url_prefix;
url += _coreapi_dll.get_filename();
core_urls.push_back(url);
// And before we try that, we'll try two mirrors, at random.
vector_string mirrors;
choose_random_mirrors(mirrors, 2);
for (vector_string::iterator si = mirrors.begin();
si != mirrors.end();
++si) {
url = (*si) + _coreapi_dll.get_filename();
core_urls.push_back(url);
}
// The very first thing we'll try is the super_mirror, if we have one.
if (!_super_mirror_url_prefix.empty()) {
url = _super_mirror_url_prefix + _coreapi_dll.get_filename();
core_urls.push_back(url);
}
// Now pick URL's off the list, and try them, until we have success.
Filename pathname = Filename::from_os_specific(_coreapi_dll.get_pathname(_root_dir));
pathname.make_dir();
HTTPClient *http = HTTPClient::get_global_ptr();
bool success = false;
while (!core_urls.empty()) {
url = core_urls.back();
core_urls.pop_back();
PT(HTTPChannel) channel = http->get_document(url);
if (!channel->download_to_file(pathname)) {
cerr << "Unable to download " << url << "\n";
} else if (!_coreapi_dll.full_verify(_root_dir)) {
cerr << "Mismatched download for " << url << "\n";
} else {
// successfully downloaded!
success = true;
break;
}
}
if (!success) {
return false;
}
// Since we had to download some of it, might as well ask the core API to
// check all of it.
if (_verify_contents == P3D_VC_none) {
_verify_contents = P3D_VC_normal;
}
return true;
}
/**
* Reports the available command-line options.
*/
void Panda3D::
usage() {
cerr
<< "\nThis is panda3d version "
<< P3D_PLUGIN_MAJOR_VERSION << "."
<< P3D_PLUGIN_MINOR_VERSION << "."
<< P3D_PLUGIN_SEQUENCE_VERSION;
#ifndef PANDA_OFFICIAL_VERSION
cerr << "c";
#endif
cerr
<< "\n\nUsage:\n"
<< " panda3d [opts] file.p3d [args]\n"
<< " panda3d -m [opts] file_a.p3d file_b.p3d [file_c.p3d ...]\n\n"
<< "This program is used to execute a Panda3D application bundle stored\n"
<< "in a .p3d file. In the first form, without the -m option, it\n"
<< "executes one application; remaining arguments following the\n"
<< "application name are passed into the application. In the second\n"
<< "form, with the -m option, it can execute multiple applications\n"
<< "simultaneously, though in this form arguments cannot be passed into\n"
<< "the applications.\n\n"
<< "Options:\n\n"
<< " -m\n"
<< " Indicates that multiple application filenames will be passed on\n"
<< " the command line. All applications will be run at the same\n"
<< " time, but additional arguments may not be passed to any of the\n"
<< " applictions.\n\n"
<< " -t token=value\n"
<< " Defines a web token or parameter to pass to the application(s).\n"
<< " This simulates a <param> entry in an <object> tag.\n\n"
<< " -w [toplevel|embedded|fullscreen|hidden]\n"
<< " Specify the type of graphic window to create. If you specify\n"
<< " \"embedded\", a new window is created to be the parent.\n\n"
<< " -s width,height\n"
<< " Specify the size of the graphic window.\n\n"
<< " -o x,y\n"
<< " Specify the position (origin) of the graphic window on the\n"
<< " screen, or on the parent window. If you specify -1,-1\n"
<< " the default position will be used, and a value of -2,-2\n"
<< " means that the window will be centered on the screen.\n\n"
<< " -l log_dirname\n"
<< " Specify the full path to the directory in which log files are\n"
<< " to be written. If this is not specified, the default is to send\n"
<< " the application output to the console.\n\n"
<< " -n\n"
<< " Allow a network connect to the Panda3D download server, to check\n"
<< " if a new version is available (but only if the current version\n"
<< " appears to be out-of-date). The default behavior, if both -n\n"
<< " and -f are omitted, is not to contact the server at all, unless\n"
<< " the local contents do not exist or cannot be read.\n\n"
<< " -f\n"
<< " Force an initial contact of the Panda3D download server, even\n"
<< " if the local contents appear to be current. This is mainly\n"
<< " useful when testing local republishes.\n\n"
<< " -i\n"
<< " Runs the application interactively. This requires that the application\n"
<< " was built with -D on the packp3d command line. If so, this option will\n"
<< " create an interactive Python prompt after the application has loaded.\n"
<< " It will also retain the PYTHONPATH environment variable from the user's\n"
<< " environment, allowing Python files on disk to shadow the same-named\n"
<< " Python files within the p3d file, for rapid iteration on the Python\n"
<< " code.\n\n"
<< " -S\n"
<< " Runs the application with security enabled, as if it were embedded in\n"
<< " a web page.\n\n"
<< " -u url\n"
<< " Specify the URL of the Panda3D download server. This is the host\n"
<< " from which the plugin itself will be downloaded if necessary. The\n"
<< " default is \"" << _host_url << "\" .\n\n"
<< " -M super_mirror_url\n"
<< " Specifies the \"super mirror\" URL, the special URL that is consulted\n"
<< " first before downloading any package file referenced by a p3d file.\n"
<< " This is primarily intended to support pre-installing a downloadable\n"
<< " Panda3D tree on the local machine, to allow p3d applications to\n"
<< " execute without requiring an internet connection.\n\n"
<< " -p platform\n"
<< " Specify the platform to masquerade as. The default is \""
<< DTOOL_PLATFORM << "\" .\n\n"
<< " -V\n"
<< " Output only the plugin version string and exit immediately.\n\n"
<< " -U\n"
<< " Output only the plugin host URL and exit immediately.\n\n"
<< " -P\n"
<< " Output only the plugin platform string and exit immediately.\n\n";
}