Added the NativeClient port code.

Thanks to Evgeniy Stepanov for providing the patch.
This commit is contained in:
Mark de Wever 2011-12-17 16:37:19 +00:00
parent 828550e58d
commit 6c6bf6e3a2
26 changed files with 1123 additions and 6 deletions

28
NaCl-LICENSE Normal file
View file

@ -0,0 +1,28 @@
Copyright 2008, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -18,7 +18,8 @@ CHANGES
Large portraits, especially high ones, should now work properly. For example TRoW scenario 5 (The Oldwood) now shows Elilmaldur-Rithrandil properly even when the screen resolution is 1920x480. If there are still issues with portraits not being shown, please let us know, either in this thread or by a bug report.
[/section]
[section="Another Change"]
[section="NativeClient"]
Added the NativeClient port code, provided by Evgeniy Stepanov.
[/section]
[section="Another Change"]

View file

@ -291,6 +291,27 @@ if env["prereqs"]:
conf.CheckBoost("thread") and \
conf.CheckBoost("asio", header_only = True)
if env['host'] in ['x86_64-nacl', 'i686-nacl']:
# libppapi_cpp has a reverse dependency on the following function
env.Append(LINKFLAGS = ['-Wl,--undefined=_ZN2pp12CreateModuleEv'])
conf.CheckLib("ppapi")
conf.CheckLib("ppapi_cpp")
conf.CheckLib("nacl-mounts")
# We are linking static libraries without libtool.
# Enumerating all transitive dependencies.
conf.CheckLib("pthread")
conf.CheckLib("dl")
conf.CheckLib("SDL")
conf.CheckLib("jpeg")
conf.CheckLib("png")
conf.CheckLib("tiff")
conf.CheckLib("ogg")
conf.CheckLib("expat")
conf.CheckLib("pixman-1")
conf.CheckLib("vorbisfile")
conf.CheckLib("vorbis")
conf.CheckLib("mikmod")
have_server_prereqs = \
conf.CheckCPlusPlus(gcc_version = "3.3") and \
conf.CheckGettextLibintl() and \
@ -354,6 +375,11 @@ else:
test_env = env.Clone()
client_env = env.Clone()
if env['host'] in ['x86_64-nacl', 'i686-nacl']:
env['_LIBFLAGS'] = '-Wl,--start-group ' + env['_LIBFLAGS'] + ' -Wl,--end-group'
client_env['_LIBFLAGS'] = '-Wl,--start-group ' + client_env['_LIBFLAGS'] + ' -Wl,--end-group'
have_msgfmt = env["MSGFMT"]
if not have_msgfmt:
env["nls"] = False
@ -367,7 +393,7 @@ if not env['nls']:
#
for env in [test_env, client_env, env]:
env.Append(CPPPATH = ["#/", "#/src"])
env.Prepend(CPPPATH = ["#/", "#/src"])
env.Append(CPPDEFINES = ["HAVE_CONFIG_H"])

View file

@ -57,6 +57,7 @@ Version 1.9.12+svn:
* Fixed: hex-cut of images in :layers debug tool.
* Forward ported a new version of multiplayer chat log history dialog
* Fixed bug #19188: Turn dialog always speaks of Konrad in tutorial
* Added: NativeClient port.
Version 1.9.12:
* Language and i18n:

View file

@ -19,6 +19,7 @@ Version 1.9.12+svn:
* Miscellaneous and bug fixes:
* Forward ported a new version of multiplayer chat log history dialog
* Fixed bug #19188: Turn dialog always speaks of Konrad in tutorial
* Added NativeClient port.
Version 1.9.12:
* Language and i18n:

View file

@ -14,5 +14,5 @@ def restore_env(env, backup):
def find_include(prefixes, include_file, include_subdir, default_prefixes = True):
if default_prefixes:
prefixes = ["/usr", "/usr/local", "/sw", "/sw/local"] + prefixes
prefixes = prefixes + ["/usr", "/usr/local", "/sw", "/sw/local"]
return [(prefix, include) for prefix in prefixes for include in glob(join(prefix, "include", include_subdir, include_file))]

View file

@ -482,7 +482,11 @@ for env in [test_env, client_env, env]:
game_cpp = client_env.Object("game.cpp", EXTRA_DEFINE = not env["pool_alloc"] and "DISABLE_POOL_ALLOC" or None);
client_env.WesnothProgram("wesnoth", [game_cpp] + [libwesnoth_extras, libwesnoth_core, libwesnoth_sdl, libwesnoth, env["wesnoth_res"]], have_client_prereqs)
wesnoth_objects = [game_cpp, libwesnoth_extras, libwesnoth_core, libwesnoth_sdl,
libwesnoth, env["wesnoth_res"]]
if env["host"] in ["x86_64-nacl", "i686-nacl"]:
wesnoth_objects += [SConscript("nacl/SConscript")]
client_env.WesnothProgram("wesnoth", wesnoth_objects, have_client_prereqs)
campaignd_sources = Split("""
server/input_stream.cpp

View file

@ -25,7 +25,8 @@
#define BREAKPOINT() __debugbreak()
#define WES_HALT() do { BREAKPOINT(); exit(1); } while (false)
#elif defined(__GNUG__) && (defined(__i386__) || defined(__x86_64__))
#elif defined(__GNUG__) && (defined(__i386__) || defined(__x86_64__)) \
&& !defined(__native_client__)
#define BREAKPOINT() asm("int3")
#define WES_HALT() do { BREAKPOINT(); abort(); } while (false)

View file

@ -261,6 +261,26 @@ void get_files_in_dir(const std::string &directory,
}
}
#ifdef __native_client__
// For performance reasons, on NaCl we only keep preferences and saves in persistent storage.
std::string get_prefs_file()
{
return "/wesnoth-userdata/preferences";
}
std::string get_save_index_file()
{
return "/wesnoth-userdata/save_index.gz";
}
std::string get_saves_dir()
{
const std::string dir_path = "/wesnoth-userdata/saves";
return get_dir(dir_path);
}
#else
std::string get_prefs_file()
{
return get_user_config_dir() + "/preferences";
@ -276,6 +296,7 @@ std::string get_saves_dir()
const std::string dir_path = get_user_data_dir() + "/saves";
return get_dir(dir_path);
}
#endif
std::string get_addon_campaigns_dir()
{

View file

@ -588,7 +588,12 @@ void init_custom_malloc();
}
#endif
#ifdef __native_client__
int wesnoth_main(int argc, char** argv)
#else
int main(int argc, char** argv)
#endif
{
#ifdef HAVE_VISUAL_LEAK_DETECTOR
@ -672,4 +677,3 @@ int main(int argc, char** argv)
return 0;
} // end main

7
src/nacl/SConscript Normal file
View file

@ -0,0 +1,7 @@
Import("env")
env.Append(CPPPATH=["#/src/nacl/generated", "#/src/nacl"])
libwesnoth_nacl = env.Library("wesnoth_nacl", ["plugin.cc"])
Return("libwesnoth_nacl")

2
src/nacl/dir_list.h Normal file
View file

@ -0,0 +1,2 @@
/* Intentionally empty */

2
src/nacl/file_list.h Normal file
View file

@ -0,0 +1,2 @@
/* Intentionally empty */

2
src/nacl/pack_list.h Normal file
View file

@ -0,0 +1,2 @@
/* Intentionally empty */

264
src/nacl/plugin.cc Normal file
View file

@ -0,0 +1,264 @@
// Copyright (c) 2011 The Native Client Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the NaCl-LICENSE file.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <ppapi/cpp/instance.h>
#include <ppapi/cpp/module.h>
#include <ppapi/cpp/rect.h>
#include <ppapi/cpp/size.h>
#include <ppapi/cpp/file_system.h>
#include <SDL_video.h>
extern int wesnoth_main(int argc, char **argv);
#include <SDL.h>
#include <SDL_nacl.h>
#include <nacl-mounts/base/KernelProxy.h>
#include <nacl-mounts/base/MainThreadRunner.h>
#include <nacl-mounts/http2/HTTP2Mount.h>
#include <nacl-mounts/pepper/PepperMount.h>
const char* http_dirs[] = {
#include <dir_list.h>
};
struct http_file_info {
const char* path;
size_t size;
} http_files[] = {
#include <file_list.h>
};
struct http_pack_info {
const char* path;
const char* pack_path;
off_t offset;
} http_packs[] = {
#include <pack_list.h>
};
class PluginInstance : public pp::Instance {
public:
explicit PluginInstance(PP_Instance instance) : pp::Instance(instance),
sdl_main_thread_(0),
width_(0),
height_(0),
progress_handler_(this),
directory_reader_(this) {
RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
proxy_ = KernelProxy::KPInstance();
runner_ = new MainThreadRunner(this);
fprintf(stderr, "Requesting an HTML5 local persistent filesystem.\n");
fflush(stderr);
fs_ = new pp::FileSystem(this, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
}
~PluginInstance() {
if (sdl_main_thread_) {
pthread_join(sdl_main_thread_, NULL);
}
}
virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
fprintf(stderr, "did change view, new %dx%d, old %dx%d\n",
position.size().width(), position.size().height(),
width_, height_);
fflush(stderr);
width_ = position.size().width();
height_ = position.size().height();
SDL_NACL_SetInstance(pp_instance(), width_, height_);
if (sdl_thread_started_ == false) {
// It seems this call to SDL_Init is required. Calling from
// sdl_main() isn't good enough.
// Perhaps it must be called from the main thread?
int lval = SDL_Init(SDL_INIT_AUDIO);
assert(lval >= 0);
if (0 == pthread_create(&sdl_main_thread_, NULL, sdl_thread_static, this)) {
sdl_thread_started_ = true;
}
}
}
bool HandleInputEvent(const pp::InputEvent& event) {
SDL_NACL_PushEvent(event);
return true;
}
void HandleMessage(const pp::Var& message) {
std::string s = message.AsString();
directory_reader_.HandleResponse(s);
}
bool Init(int argc, const char* argn[], const char* argv[]) {
return true;
}
private:
bool sdl_thread_started_;
pthread_t sdl_main_thread_;
int width_;
int height_;
KernelProxy* proxy_;
MainThreadRunner* runner_;
pp::FileSystem* fs_;
static void* sdl_thread_static(void* param) {
return reinterpret_cast<PluginInstance*>(param)->sdl_thread();
}
void* sdl_thread() {
fprintf(stderr, "Initializing nacl-mounts.\n");
fflush(stderr);
// Setup writable homedir.
PepperMount* pepper_mount = new PepperMount(runner_, fs_, 20 * 1024 * 1024);
pepper_mount->SetDirectoryReader(&directory_reader_);
pepper_mount->SetPathPrefix("/wesnoth-userdata");
proxy_->mkdir("/wesnoth-userdata", 0777);
int res = proxy_->mount("/wesnoth-userdata", pepper_mount);
// The following lines can be removed when nacl-mounts starts intercepting mkdir() calls.
proxy_->mkdir("/wesnoth-userdata/saves", 0777);
// Setup r/o data directory in /usr/local/share/wesnoth
HTTP2Mount* http2_mount = new HTTP2Mount(runner_, "./usr/local/share/wesnoth");
http2_mount->SetLocalCache(fs_, 350*1024*1024, "/wesnoth0", true);
http2_mount->SetProgressHandler(&progress_handler_);
fprintf(stderr, "Registering known files.\n");
fflush(stderr);
for (int i = 0; i < sizeof(http_dirs) / sizeof(*http_dirs); ++i) {
char* path = (char*)http_dirs[i];
if (path && *path)
http2_mount->AddDir(path);
}
for (int i = 0; i < sizeof(http_files) / sizeof(*http_files); ++i) {
char* path = (char*)http_files[i].path;
size_t size = http_files[i].size;
if (path && *path)
http2_mount->AddFile(path, size);
}
for (int i = 0; i < sizeof(http_packs) / sizeof(*http_packs); ++i) {
char* path = (char*)http_packs[i].path;
char* pack_path = (char*)http_packs[i].pack_path;
off_t offset = http_packs[i].offset;
if (path && *path) {
http2_mount->SetInPack(path, pack_path, offset);
}
}
http2_mount->SetInMemory("/fonts/Andagii.ttf", true);
http2_mount->SetInMemory("/fonts/DejaVuSans.ttf", true);
http2_mount->SetInMemory("/fonts/wqy-zenhei.ttc", true);
fprintf(stderr, "Mounting the filesystem.\n");
fflush(stderr);
proxy_->mkdir("/usr", 0777);
proxy_->mkdir("/usr/local", 0777);
proxy_->mkdir("/usr/local/share", 0777);
res = proxy_->mount("/usr/local/share/wesnoth", http2_mount);
if (!res) {
fprintf(stderr, "FS initialization success.\n");
} else {
fprintf(stderr, "FS initialization failure.\n");
}
fflush(stderr);
// Finally, launch the game.
char res_s[100];
snprintf(res_s, sizeof(res_s), "%dx%d", width_, height_);
static char const * argv[] = {"wesnoth", "-r", res_s, NULL};
printf("starting game thread: %s\n", res_s);
wesnoth_main(sizeof(argv) / sizeof(*argv) - 1, (char**)argv);
return NULL;
}
class ProgressHandler : public HTTP2ProgressHandler {
public:
pp::Instance* instance_;
ProgressHandler(pp::Instance* instance) : instance_(instance) {}
void HandleProgress(std::string& path, int64_t bytes, int64_t size) {
char buf[100];
snprintf(buf, sizeof(buf), "%llu,%llu", (unsigned long long)bytes,
(unsigned long long)size);
std::string message = "[\"" + path + "\"," + buf + "]";
instance_->PostMessage(message);
}
};
ProgressHandler progress_handler_;
class JSDirectoryReader: public DirectoryReader {
public:
pp::Instance* instance_;
pp::CompletionCallback cc_;
std::set<std::string>* entries_;
JSDirectoryReader(pp::Instance* instance) : instance_(instance) {}
int ReadDirectory(const std::string& path, std::set<std::string>* entries, const pp::CompletionCallback& cc) {
cc_ = cc;
entries_ = entries;
std::string message = "[\"ReadDirectory\",\"" + path + "\"]";
instance_->PostMessage(message);
}
void HandleResponse(const std::string& response) {
fprintf(stderr, "response: %s\n", response.c_str());
std::string::const_iterator ind = response.begin();
std::string::const_iterator next = response.begin();
while (ind != response.end() && next != response.end()) {
if (*next == '\n' && ind != next) {
if (*ind == '\n') {
++ind;
}
if (ind != next) {
entries_->insert(std::string(ind, next));
}
ind = next;
}
++next;
}
if (ind != next) {
std::string last(ind, next-1);
if (!last.empty()) {
entries_->insert(last);
}
}
cc_.Run(PP_OK);
}
};
JSDirectoryReader directory_reader_;
};
class PepperModule : public pp::Module {
public:
// Create and return a PluginInstanceInstance object.
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new PluginInstance(instance);
}
};
namespace pp {
Module* CreateModule() {
return new PepperModule();
}
} // namespace pp

15
utils/nacl/build.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
# Copyright (c) 2011 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the NaCl-LICENSE file.
set -e -x
ROOT=${NACL_TOOLCHAIN_ROOT:-$HOME/root/nacl-sdk}
PATH=$ROOT/bin:$ROOT/x86_64-nacl/usr/bin:$PATH
PKG_CONFIG_PATH=$ROOT/x86_64-nacl/usr/lib/pkgconfig scons -j15 host=x86_64-nacl \
boostdir=$ROOT/x86_64-nacl/usr/include/boost \
boostlibdir=$ROOT/x86_64-nacl/usr/lib sdldir=$ROOT/x86_64-nacl/usr nls=no \
destdir=$ROOT/x86_64-nacl \
build=release wesnoth install

15
utils/nacl/build32.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
# Copyright (c) 2011 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the NaCl-LICENSE file.
set -e -x
ROOT=${NACL_TOOLCHAIN_ROOT:-$HOME/root/nacl-sdk}
PATH=$ROOT/bin:$ROOT/i686-nacl/usr/bin:$PATH
PKG_CONFIG_PATH=$ROOT/i686-nacl/usr/lib/pkgconfig scons -j15 host=i686-nacl \
boostdir=$ROOT/i686-nacl/usr/include/boost \
boostlibdir=$ROOT/i686-nacl/usr/lib sdldir=$ROOT/i686-nacl/usr nls=no \
destdir=$ROOT/i686-nacl \
build=release wesnoth

145
utils/nacl/buildpack.py Normal file
View file

@ -0,0 +1,145 @@
#!/usr/bin/python
# Copyright (c) 2011 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the NaCl-LICENSE file.
import shutil
import glob
import os
import fnmatch
import sys
# pack description format: [root_mask, file_name_mask, file_path_exclusion_mask]
# pack will contain
# all files under (and including) the expansion of root_mask,
# whose names match file_name_mask,
# whose full path (with root_mask) does not match full_path_exclusion_mask
pack0_masks = [
['data/languages', '*', ''],
['data/hardwired', '*', ''],
['images/game-icon.png', '*', ''],
['images/cursors-bw', '*', ''],
['images/misc/logo.png', '*', ''],
]
pack1_masks = [
['images', '*', ''],
['data/*.cfg', '*', ''],
['data/gui', '*', ''],
['data/themes', '*.cfg', ''],
['data/core', '*.cfg', ''],
['data/COPYING.txt', '*', ''],
['data/core/images/maps/wesnoth.png', '*', ''],
['data/ai', '*', ''],
['data/campaigns', '_main.cfg', ''],
['sounds/button.wav', '*', ''],
['sounds/select.wav', '*', ''],
]
# These files are needed to display the campaign list.
pack2_masks = [
['data/core/images/misc', '*', ''],
['data/campaigns', 'campaign_image.png', ''],
['data/campaigns', 'campaign_image.jpg', ''],
['data/core/images/units/human-loyalists/knight.png', '*', ''],
['data/core/images/units/elves-wood/lord.png', '*', ''],
['data/core/images/units/human-outlaws/fugitive.png', '*', ''],
['data/core/images/units/elves-wood/high-lord.png', '*', ''],
['data/core/images/units/human-loyalists/general.png', '*', ''],
['data/core/images/units/human-magi/elder-mage.png', '*', ''],
['data/core/images/units/undead/soulless-swimmer.png', '*', ''],
['data/core/images/units/orcs/ruler.png', '*', ''],
['data/campaigns/Heir_To_The_Throne/images/units/konrad-lord-leading.png', '*', ''],
['data/campaigns/The_South_Guard/images/deoran/horseman-commander-defend.png', '*', ''],
['data/campaigns/Descent_Into_Darkness/images/units/dark-mage.png', '*', ''],
['data/campaigns/The_Rise_Of_Wesnoth/images/units/noble-lord.png', '*', ''],
['data/campaigns/Under_the_Burning_Suns/images/units/elves-desert/kaleh.png', '*', ''],
['data/core/images/items/hammer-runic.png', '*', ''],
['data/core/images/items/sceptre-of-fire.png', '*', ''],
['data/core/images/scenery/dwarven-doors-closed.png', '*', ''],
]
pack3_masks = [
['data', '*.cfg', ''],
['data/core/images/terrain', '*', ''],
['data/core/images/themes', '*', ''],
['data/lua', '*', ''],
['sounds', '*', ''],
]
pack4_masks = [
['data/core/sounds', '*', ''],
]
packs = [pack0_masks, pack1_masks, pack2_masks, pack3_masks, pack4_masks]
all_files = set()
def list_path_with_mask(path, mask, exclude_mask):
files = set()
if os.path.isdir(path):
for (dirpath, dirnames, filenames) in os.walk(path):
for filename in filenames:
if fnmatch.fnmatch(filename, mask) and not fnmatch.fnmatch(os.path.join(dirpath, filename), exclude_mask):
files.add(os.path.join(dirpath, filename))
else:
if fnmatch.fnmatch(path, mask) and not fnmatch.fnmatch(path, exclude_mask):
files.add(path)
return files
def list_pack_contents(masks):
files = set()
for (root_path, mask, exclude_mask) in masks:
roots = glob.glob(root_path)
for root in roots:
new_files = list_path_with_mask(root, mask, exclude_mask).difference(all_files)
files.update(new_files)
all_files.update(new_files)
return files
def build_pack(files, out):
fout = open(out, "w")
out_list = []
sz = 0
for f in files:
data = open(f).read()
fout.write(data)
out_list.append('{"/%s", "/%s", %d},\n' % (f, out, sz))
sz += len(data)
fout.close()
print '%s: %d files, %d bytes total' % (out, len(files), sz)
return ''.join(out_list)
base_dir = sys.argv[1]
out_list = os.path.join(os.getcwd(), 'src/nacl/generated/pack_list.h')
os.chdir(base_dir)
# define additional packs
packs.append([['data/core/images/units', '*', ''], ['data/core/images/attacks', '*', '']])
for path in glob.glob(os.path.join(base_dir, 'data/campaigns/*')):
path = path[len(base_dir):]
print 'Campaign: ' + path
packs.append([[path, '*', '']])
packs.append([['data/core/images/portraits/humans', '*', '']])
packs.append([['data/core/images/portraits', '*', '']])
packs.append([['data/core/images', '*', '']])
packs.append([['data', '*', 'data/core/music/*']])
# build packs
fout_list = open(out_list, 'w')
for (index, pack) in enumerate(packs):
pack_files = list_pack_contents(pack)
out_list_data = build_pack(pack_files, 'pack' + str(index))
fout_list.write(out_list_data)
fout_list.write('{"", "", 0}\n')
fout_list.close()

13
utils/nacl/genfs.sh Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
# Copyright (c) 2011 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the NaCl-LICENSE file.
BASE=$NACL_SDK_ROOT/toolchain/linux_x86/x86_64-nacl
DIRS_OUT=src/nacl/generated/dir_list.h
FILES_OUT=src/nacl/generated/file_list.h
(cd $BASE/usr/local/share/wesnoth; find -type d) | perl -pe 's/^\.//' | perl -pe 's/^(.*)$/"$1",/' >$DIRS_OUT
echo "" >>$DIRS_OUT
(cd $BASE/usr/local/share/wesnoth; find -type f -printf "%p %s\n") | perl -pe 's/^\.//' | perl -pe 's/^(.*) (\d+)$/{"$1", $2},/' >$FILES_OUT
echo '{"", 0}' >>$FILES_OUT

41
utils/nacl/howto Normal file
View file

@ -0,0 +1,41 @@
Building Wesnoth for NativeClient.
1. Get the NaCl SDK from http://code.google.com/chrome/nativeclient/
Setup enviroment:
NACL_SDK_ROOT=<path to sdk>
NACL_TOOLCHAIN_ROOT=$NACL_SDK_ROOT/pepper_15/toolchain/linux_x86
2. Get naclports: http://code.google.com/p/naclports/
Build the ports for both x86_64 and i686:
make NACL_GLIBC=1 NACL_PACKAGES_BITSIZE=32
make NACL_GLIBC=1 NACL_PACKAGES_BITSIZE=64
This will install all prerequisites under $NACL_TOOLCHAIN_ROOT.
3. Build Wesnoth.
./utils/nacl/build.sh # build and install under $NACL_TOOLCHAIN_ROOT
# Scan installed files, pack them in bundles.
# You can't realistically download 13000 small files over HTTP.
mkdir -p src/nacl/generated
./utils/nacl/buildpack.py $NACL_TOOLCHAIN_ROOT/x86_64-nacl/usr/local/share/wesnoth/
./utils/nacl/genfs
# Rebuild Wesnoth with the generated file metadata.
./utils/nacl/build.sh
# Build 32-bit version. No need to repeat the scanning step.
./utils/nacl/build32.sh
cp wesnoth $NACL_TOOLCHAIN_ROOT/x86_64-nacl/usr/local/share/bin/wesnoth32
4. Deploy
Copy stuff from utils/nacl/static to $NACL_TOOLCHAIN_ROOT/x86_64-nacl
./install.sh inst
Upload inst/ to any static file hosting.
Open wesnoth.html in Chromium >= 15. At the moment you need to either enable NativeClient in
about:flags, or (preferred) access it through Chrome Store.
Warning: NaCl app will download its data with xmlHttpRequest(). This is not compatible with
CDNs that use HTTP 302 redirects to another domain for load balancing and do not set CORS
headers appropriately.

48
utils/nacl/install.sh Executable file
View file

@ -0,0 +1,48 @@
#!/bin/bash
# Copyright (c) 2011 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the NaCl-LICENSE file.
set -e
DST=$1
if [ z$DST == z ]; then
echo "Need dstdir"
exit 1
fi
if [ -d $DST ]; then
echo "$DST already exists"
exit 1
fi
ROOT=$NACL_TOOLCHAIN_ROOT/x86_64-nacl
WESNOTH=$ROOT/usr/local/share/wesnoth
mkdir $DST
cp -v $ROOT/wesnoth.html $DST/
cp -v $ROOT/wesnoth.nmf $DST/
cp -v $ROOT/wesnoth.js $DST/
cp -v $ROOT/check_browser.js $DST/
cp -v $ROOT/peppermount_helper.js $DST/
mkdir -p $DST/usr/local/bin/
cp -v $ROOT/usr/local/bin/wesnoth $ROOT/usr/local/bin/wesnoth32 $DST/usr/local/bin/
mkdir $DST/lib32 $DST/lib64
for lib in `cat $ROOT/wesnoth.nmf | grep '"url": "lib32' | perl -pe 's/^.*?url": "lib32\/(.*)".*/$1/'`; do
cp -v $ROOT/lib32/$lib $DST/lib32/$lib
done
for lib in `cat $ROOT/wesnoth.nmf | grep '"url": "lib64' | perl -pe 's/^.*?url": "lib64\/(.*)".*/$1/'`; do
cp -v $ROOT/lib64/$lib $DST/lib64/$lib
done
mkdir -p $DST/usr/local/share/wesnoth
cp -v $ROOT/usr/local/share/wesnoth/pack* $DST/usr/local/share/wesnoth/
mkdir -p $DST/usr/local/share/wesnoth/data/core/music/
cp -rv $ROOT/usr/local/share/wesnoth/data/core/music/* $DST/usr/local/share/wesnoth/data/core/music/
mkdir -p $DST/usr/local/share/wesnoth/fonts/
cp -rv $ROOT/usr/local/share/wesnoth/fonts/* $DST/usr/local/share/wesnoth/fonts/

View file

@ -0,0 +1,178 @@
/*
* Copyright (c) 2011 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the NaCl-LICENSE file.
*/
/**
* @fileoverview This file provides a BrowserChecker Javascript class.
* Users can create a BrowserChecker object, invoke checkBrowser(|version|),
* and then use getIsValidBrowser() and getBrowserSupportStatus()
* to determine if the browser version is greater than |version|
* and if the Native Client plugin is found.
*/
// Create a namespace object
var browser_version = browser_version || {};
/**
* Class to provide checking for version and NativeClient.
* @param {integer} arg1 An argument that indicates major version of Chrome we
* require, such as 14.
*/
/**
* Constructor for the BrowserChecker. Sets the major version of
* Chrome that is required to |minChromeVersion|.
* @param minChromeVersion The earliest major version of chrome that
* is supported. If the Chrome browser version is less than
* |minChromeVersion| then |isValidBrowswer| will be set to false.
* @param opt_maxChromeVersion Ignored. Retained for backwards compatibility.
* @param appVersion The application version string.
* @param plugins The plugins that exist in the browser.
* @constructor
*/
browser_version.BrowserChecker = function(minChromeVersion,
appVersion, plugins,
opt_maxChromeVersion) {
/**
* Version specified by the user. This class looks to see if the browser
* version is >= |minChromeVersion_|.
* @type {integer}
* @private
*/
this.minChromeVersion_ = minChromeVersion;
/**
* List of Browser plugin objects.
* @type {Ojbect array}
* @private
*/
this.plugins_ = plugins;
/**
* Application version string from the Browser.
* @type {integer}
* @private
*/
this.appVersion_ = appVersion;
/**
* Flag used to indicate if the browser has Native Client and is if the
* browser version is recent enough.
* @type {boolean}
* @private
*/
this.isValidBrowser_ = false;
/**
* Actual major version of Chrome -- found by querying the browser.
* @type {integer}
* @private
*/
this.chromeVersion_ = null;
/**
* Browser support status. This allows the user to get a detailed status
* rather than using this.browserSupportMessage.
*/
this.browserSupportStatus_ =
browser_version.BrowserChecker.StatusValues.UNKNOWN;
}
/**
* The values used for BrowserChecker status to indicate success or
* a specific error.
* @enum {id}
*/
browser_version.BrowserChecker.StatusValues = {
UNKNOWN: 0,
NACL_ENABLED: 1,
UNKNOWN_BROWSER: 2,
CHROME_VERSION_TOO_OLD: 3,
NACL_NOT_ENABLED: 4,
NOT_USING_SERVER: 5
};
/**
* Determines if the plugin with name |name| exists in the browser.
* @param {string} name The name of the plugin.
* @param {Object array} plugins The plugins in this browser.
* @return {bool} |true| if the plugin is found.
*/
browser_version.BrowserChecker.prototype.pluginExists = function(name,
plugins) {
for (var index=0; index < plugins.length; index++) {
var plugin = this.plugins_[index];
var plugin_name = plugin['name'];
// If the plugin is not found, you can use the Javascript console
// to see the names of the plugins that were found when debugging.
if (plugin_name.indexOf(name) != -1) {
return true;
}
}
return false;
}
/**
* Returns browserSupportStatus_ which indicates if the browser supports
* Native Client. Values are defined as literals in
* browser_version.BrowserChecker.StatusValues.
* @ return {int} Level of NaCl support.
*/
browser_version.BrowserChecker.prototype.getBrowserSupportStatus = function() {
return this.browserSupportStatus_;
}
/**
* Returns isValidBrowser (true/false) to indicate if the browser supports
* Native Client.
* @ return {bool} If this browser has NativeClient and correct version.
*/
browser_version.BrowserChecker.prototype.getIsValidBrowser = function() {
return this.isValidBrowser_;
}
/**
* Checks to see if this browser can support Native Client applications.
* For Chrome browsers, checks to see if the "Native Client" plugin is
* enabled.
*/
browser_version.BrowserChecker.prototype.checkBrowser = function() {
var versionPatt = /Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/;
var result = this.appVersion_.match(versionPatt);
// |result| stores the Chrome version number.
if (!result) {
this.isValidBrowser_ = false;
this.browserSupportStatus_ =
browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER;
} else {
this.chromeVersion_ = result[1];
// We know we have Chrome, check version and/or plugin named Native Client
if (this.chromeVersion_ >= this.minChromeVersion_) {
var found_nacl = this.pluginExists('Native Client', this.plugins_);
if (found_nacl) {
this.isValidBrowser_ = true;
this.browserSupportStatus_ =
browser_version.BrowserChecker.StatusValues.NACL_ENABLED;
} else {
this.isValidBrowser_ = false;
this.browserSupportStatus_ =
browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED;
}
} else {
// We are in a version that is less than |minChromeVersion_|
this.isValidBrowser_ = false;
this.browserSupportStatus_ =
browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD;
}
}
var my_protocol = window.location.protocol;
if (my_protocol.indexOf('file') == 0) {
this.isValidBrowser_ = false;
this.browserSupportStatus_ =
browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER;
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2011 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the NaCl-LICENSE file.
*/
function toArray(list) {
return Array.prototype.slice.call(list || [], 0);
}
function errorHandler(e) {
var msg = '';
switch (e.code) {
case FileError.QUOTA_EXCEEDED_ERR:
msg = 'QUOTA_EXCEEDED_ERR';
break;
case FileError.NOT_FOUND_ERR:
msg = 'NOT_FOUND_ERR';
break;
case FileError.SECURITY_ERR:
msg = 'SECURITY_ERR';
break;
case FileError.INVALID_MODIFICATION_ERR:
msg = 'INVALID_MODIFICATION_ERR';
break;
case FileError.INVALID_STATE_ERR:
msg = 'INVALID_STATE_ERR';
break;
default:
msg = 'Unknown Error';
break;
};
console.log('Error: ' + msg);
document.getElementById('wesnoth').postMessage("");
}
function listResults(entries) {
s = ''
entries.forEach(function(entry, i) {
s += entry.name;
s += '\n';
});
console.log('entries: ' + s);
document.getElementById('wesnoth').postMessage(s);
}
function readDir(dir) {
var dirReader = dir.createReader();
var entries = [];
// Call the reader.readEntries() until no more results are returned.
var readEntries = function() {
dirReader.readEntries (function(results) {
if (!results.length) {
listResults(entries.sort());
} else {
entries = entries.concat(toArray(results));
readEntries();
}
}, errorHandler);
};
readEntries(); // Start reading dirs.
}
function HandlePepperMountMessage(data) {
path = data[1];
onInitFs = function(fs) {
fs.root.getDirectory(path, {}, readDir, errorHandler);
};
window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024 /*5MB*/, onInitFs, errorHandler);
}

View file

@ -0,0 +1,28 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<!--
Copyright (c) 2011 The Native Client Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the NaCl-LICENSE file.
-->
<head>
<title>Wesnoth NativeClient demo</title>
<script type="text/javascript" src="check_browser.js"></script>
</head>
<body style="color:gray;background-color:#776655">
<div id="nacl_div" style="text-align:center">
<canvas id="progress_bar"
width="1024" height="20">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
<script src="peppermount_helper.js"></script>
<script src="wesnoth.js"></script>
<p style="text-align:center">
<a href="http://www.wesnoth.org">Wesnoth</a> |
<a href="http://code.google.com/chrome/nativeclient">NativeClient</a>
</body>
</HTML>

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2011 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the NaCl-LICENSE file.
*/
function updateProgressBar(percent, message, styleBackground,
styleForeground, styleText) {
console.log("message: " + message);
console.log("progress: " + percent + "%");
var progress_bar =
document.getElementById('progress_bar');
var ctx = progress_bar.getContext('2d');
var width = progress_bar.width;
ctx.fillStyle = styleForeground ? styleForeground :
"#52565a";
ctx.fillRect(0, 0, percent * width, 20);
ctx.fillStyle = styleBackground ? styleBackground :
"#ddccbb";
ctx.fillRect(percent * width, 0, width, 20);
ctx.fillStyle = styleText ? styleText : "black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = 'sans-serif';
ctx.fillText(message, width / 2, 10, 3 * width / 4);
}
function HandleProgress(event) {
var loadPercent = 0.0;
var loadPercentString;
if (event.lengthComputable && event.total > 0) {
loadPercent = event.loaded / event.total;
} else {
// The total length is not yet known.
loadPercent = -1.0;
}
updateProgressBar(loadPercent, "Initializing Wesnoth (please be patient) ...");
}
function HandleMessage(event) {
console.log(event.data);
var data = JSON.parse(event.data);
console.log(data)
console.log(data[0])
console.log(data[0] == 'ReadDirectory')
if (data[0] == 'ReadDirectory') {
HandlePepperMountMessage(data);
} else {
updateProgressBar(data[1] / data[2], data[0]);
}
}
function updateStatusFail(message) {
updateProgressBar(1, message, 'black',
'red', 'black')
}
function moduleLoadError() {
var msg = "NaCl module load error: " + document.getElementById('wesnoth').lastError;
updateStatusFail(msg);
console.log(msg);
}
function moduleLoadAbort() {
var msg = "NaCl module load aborted: " + document.getElementById('wesnoth').lastError;
updateStatusFail(msg);
console.log(msg);
}
function requestQuotaAndStartWesnoth() {
quota_required = 380*1024*1024;
window.webkitStorageInfo.requestQuota(PERSISTENT, quota_required, function(bytes) {
console.log("Persistent storage quota granted: " + bytes + " bytes");
if (bytes >= quota_required) {
var embed = document.createElement('embed');
embed.setAttribute('name', 'nacl_module');
embed.setAttribute('id', 'wesnoth');
embed.setAttribute('width', 1024);
embed.setAttribute('height', 800);
embed.setAttribute('src', 'wesnoth.nmf');
embed.setAttribute('type', 'application/x-nacl');
var div = document.getElementById("nacl_div");
div.appendChild(embed);
div.addEventListener('progress', HandleProgress, true);
div.addEventListener('message', HandleMessage, true);
div.addEventListener('error', moduleLoadError, true);
div.addEventListener('abort', moduleLoadAbort, true);
} else {
updateStatusFail("Unsufficient HTML5 file system quota: " + bytes + " bytes");
console.log("Unsufficient HTML5 file system quota: " + bytes + " bytes");
}
}, function(e) {
updateStatusFail("HTML5 file system quota request failed");
console.log("Quota request error: " + e);
});
}
function checkBrowser() {
var isValidBrowser = false;
var browserSupportStatus = 0;
var checker = new browser_version.BrowserChecker(15, // Minumum Chrome version.
navigator["appVersion"],
navigator["plugins"]);
checker.checkBrowser();
isValidBrowser = checker.getIsValidBrowser();
browserSupportStatus = checker.getBrowserSupportStatus();
switch (browserSupportStatus) {
case browser_version.BrowserChecker.StatusValues.NACL_ENABLED:
console.log('Native Client plugin enabled.');
break;
case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER:
updateStatusFail('UNKNOWN BROWSER');
break;
case browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD:
console.log('Chrome too old: You must use Chrome version 15 or later.');
updateStatusFail('NEED CHROME 15 OR LATER');
break;
case browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED:
console.log(
'NaCl disabled: Native Client is not enabled.<br>' +
'Please go to <b>chrome://plugins</b> and enable Native Client ' +
'plugin.');
updateStatusFail('NativeClient NOT ENABLED');
break;
case browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER:
console.log(
'file: URL detected, please use a web server to host Native ' +
'Client applications.');
updateStatusFail('file:// URLs NOT ALLOWED');
default:
console.log('Unknown error: Unable to detect browser and/or ' +
'Native Client support.');
updateStatusFail('UNKNOWN ERROR');
break;
}
return isValidBrowser && browserSupportStatus == browser_version.BrowserChecker.StatusValues.NACL_ENABLED;
}
if (checkBrowser())
requestQuotaAndStartWesnoth();

View file

@ -0,0 +1,52 @@
{
"program": {
"x86-32": {"url": "lib32/runnable-ld.so"},
"x86-64": {"url": "lib64/runnable-ld.so"}
},
"files": {
"libc.so.5e6ad7ea" : {
"x86-32" : {"url": "lib32/libc.so.5e6ad7ea"},
"x86-64" : {"url": "lib64/libc.so.5e6ad7ea"}
},
"libdl.so.5e6ad7ea" : {
"x86-32" : {"url": "lib32/libdl.so.5e6ad7ea"},
"x86-64" : {"url": "lib64/libdl.so.5e6ad7ea"}
},
"librt.so.5e6ad7ea" : {
"x86-32" : {"url": "lib32/librt.so.5e6ad7ea"},
"x86-64" : {"url": "lib64/librt.so.5e6ad7ea"}
},
"libm.so.5e6ad7ea" : {
"x86-32" : { "url": "lib32/libm.so.5e6ad7ea"},
"x86-64" : { "url": "lib64/libm.so.5e6ad7ea"}
},
"libgcc_s.so.1" : {
"x86-32" : { "url": "lib32/libgcc_s.so.1"},
"x86-64" : { "url": "lib64/libgcc_s.so.1"}
},
"libpthread.so.5e6ad7ea" : {
"x86-32" : { "url": "lib32/libpthread.so.5e6ad7ea"},
"x86-64" : { "url": "lib64/libpthread.so.5e6ad7ea"}
},
"libstdc++.so.6" : {
"x86-32" : { "url": "lib32/libstdc++.so.6"},
"x86-64" : { "url": "lib64/libstdc++.so.6"}
},
"libplatform.so" : {
"x86-32" : { "url": "lib32/libplatform.so"},
"x86-64" : { "url": "lib64/libplatform.so"}
},
"libgio.so" : {
"x86-32" : { "url": "lib32/libgio.so"},
"x86-64" : { "url": "lib64/libgio.so"}
},
"libppapi_cpp.so" : {
"x86-32" : { "url": "lib32/libppapi_cpp.so"},
"x86-64" : { "url": "lib64/libppapi_cpp.so"}
},
"main.nexe" : {
"x86-32" : {"url" : "usr/local/bin/wesnoth32"},
"x86-64" : {"url" : "usr/local/bin/wesnoth"}
}
}
}