diff --git a/NaCl-LICENSE b/NaCl-LICENSE new file mode 100644 index 00000000000..1941a11f8ce --- /dev/null +++ b/NaCl-LICENSE @@ -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. diff --git a/RELEASE_NOTES b/RELEASE_NOTES index a88a17055b5..b273091e559 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -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"] diff --git a/SConstruct b/SConstruct index 903227e2a6c..d71f9069ee1 100755 --- a/SConstruct +++ b/SConstruct @@ -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"]) diff --git a/changelog b/changelog index 3581b8d35bf..4bf002433a9 100644 --- a/changelog +++ b/changelog @@ -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: diff --git a/players_changelog b/players_changelog index f982bc31d45..011fb08bcdb 100644 --- a/players_changelog +++ b/players_changelog @@ -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: diff --git a/scons/config_check_utils.py b/scons/config_check_utils.py index b566791bb7b..5484ad58b46 100644 --- a/scons/config_check_utils.py +++ b/scons/config_check_utils.py @@ -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))] diff --git a/src/SConscript b/src/SConscript index b692aa08a45..0f0576bacbb 100644 --- a/src/SConscript +++ b/src/SConscript @@ -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 diff --git a/src/asserts.hpp b/src/asserts.hpp index 40915e29325..1f312752de1 100644 --- a/src/asserts.hpp +++ b/src/asserts.hpp @@ -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) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 3dd0db13310..5554bc919f1 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -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() { diff --git a/src/game.cpp b/src/game.cpp index c50687d60ef..cfc82458504 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -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 - diff --git a/src/nacl/SConscript b/src/nacl/SConscript new file mode 100644 index 00000000000..c3e7f857ef0 --- /dev/null +++ b/src/nacl/SConscript @@ -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") diff --git a/src/nacl/dir_list.h b/src/nacl/dir_list.h new file mode 100644 index 00000000000..fe282fbde87 --- /dev/null +++ b/src/nacl/dir_list.h @@ -0,0 +1,2 @@ +/* Intentionally empty */ + diff --git a/src/nacl/file_list.h b/src/nacl/file_list.h new file mode 100644 index 00000000000..fe282fbde87 --- /dev/null +++ b/src/nacl/file_list.h @@ -0,0 +1,2 @@ +/* Intentionally empty */ + diff --git a/src/nacl/pack_list.h b/src/nacl/pack_list.h new file mode 100644 index 00000000000..fe282fbde87 --- /dev/null +++ b/src/nacl/pack_list.h @@ -0,0 +1,2 @@ +/* Intentionally empty */ + diff --git a/src/nacl/plugin.cc b/src/nacl/plugin.cc new file mode 100644 index 00000000000..fcea550afc1 --- /dev/null +++ b/src/nacl/plugin.cc @@ -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 +#include +#include + +#include +#include +#include +#include +#include + +#include +extern int wesnoth_main(int argc, char **argv); +#include +#include + +#include +#include +#include +#include + + +const char* http_dirs[] = { +#include +}; + +struct http_file_info { + const char* path; + size_t size; +} http_files[] = { +#include +}; + +struct http_pack_info { + const char* path; + const char* pack_path; + off_t offset; +} http_packs[] = { +#include +}; + + +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(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* entries_; + + JSDirectoryReader(pp::Instance* instance) : instance_(instance) {} + + int ReadDirectory(const std::string& path, std::set* 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 diff --git a/utils/nacl/build.sh b/utils/nacl/build.sh new file mode 100755 index 00000000000..7a6909836b3 --- /dev/null +++ b/utils/nacl/build.sh @@ -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 diff --git a/utils/nacl/build32.sh b/utils/nacl/build32.sh new file mode 100755 index 00000000000..d7571e2da16 --- /dev/null +++ b/utils/nacl/build32.sh @@ -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 diff --git a/utils/nacl/buildpack.py b/utils/nacl/buildpack.py new file mode 100644 index 00000000000..a603e400e06 --- /dev/null +++ b/utils/nacl/buildpack.py @@ -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() diff --git a/utils/nacl/genfs.sh b/utils/nacl/genfs.sh new file mode 100755 index 00000000000..0b3d9e0f6cd --- /dev/null +++ b/utils/nacl/genfs.sh @@ -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 + diff --git a/utils/nacl/howto b/utils/nacl/howto new file mode 100644 index 00000000000..6f6bfbfe55e --- /dev/null +++ b/utils/nacl/howto @@ -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= +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. diff --git a/utils/nacl/install.sh b/utils/nacl/install.sh new file mode 100755 index 00000000000..8f887bc73f9 --- /dev/null +++ b/utils/nacl/install.sh @@ -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/ diff --git a/utils/nacl/static/check_browser.js b/utils/nacl/static/check_browser.js new file mode 100644 index 00000000000..2e735b7f9d4 --- /dev/null +++ b/utils/nacl/static/check_browser.js @@ -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; + } +} + diff --git a/utils/nacl/static/peppermount_helper.js b/utils/nacl/static/peppermount_helper.js new file mode 100644 index 00000000000..1f542dbaef9 --- /dev/null +++ b/utils/nacl/static/peppermount_helper.js @@ -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); +} diff --git a/utils/nacl/static/wesnoth.html b/utils/nacl/static/wesnoth.html new file mode 100644 index 00000000000..54b6866454c --- /dev/null +++ b/utils/nacl/static/wesnoth.html @@ -0,0 +1,28 @@ + + + + + Wesnoth NativeClient demo + + + +
+ + Your browser does not support HTML5 Canvas. + +
+ + + + +

+ Wesnoth | + NativeClient + + diff --git a/utils/nacl/static/wesnoth.js b/utils/nacl/static/wesnoth.js new file mode 100644 index 00000000000..bf26385f4f3 --- /dev/null +++ b/utils/nacl/static/wesnoth.js @@ -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.
' + + 'Please go to chrome://plugins 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(); + diff --git a/utils/nacl/static/wesnoth.nmf b/utils/nacl/static/wesnoth.nmf new file mode 100644 index 00000000000..a4528546e4b --- /dev/null +++ b/utils/nacl/static/wesnoth.nmf @@ -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"} + } + } +}