diff --git a/.gitignore b/.gitignore
index af8057fc82b..bddbe0d8d26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -96,3 +96,10 @@ android/app/src/main/assets/files/
# Mac magic folders
.DS_Store
+
+#web build artifacts
+*.wasm
+*.wasm.map
+fheroes2.js
+fheroes2.data
+src/dist/web/dist/*
diff --git a/Makefile.web b/Makefile.web
new file mode 100644
index 00000000000..f935cbe2080
--- /dev/null
+++ b/Makefile.web
@@ -0,0 +1,37 @@
+###########################################################################
+# fheroes2: https://github.com/ihhub/fheroes2 #
+# Copyright (C) 2021 - 2024 #
+# #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program; if not, write to the #
+# Free Software Foundation, Inc., #
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
+###########################################################################
+
+# Options:
+#
+# FHEROES2_STRICT_COMPILATION: build in strict compilation mode (turns warnings into errors)
+# FHEROES2_WITH_DEBUG: build in debug mode
+# FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2)
+
+PROJECT_NAME := fheroes2
+PROJECT_VERSION := $(file < version.txt)
+
+.PHONY: all clean
+
+all:
+ $(MAKE) -C src/dist PLATFORM=web
+
+clean:
+ $(MAKE) -C src/dist PLATFORM=web clean
+ $(MAKE) -C files/lang clean
diff --git a/files/web/dist/index.html b/files/web/dist/index.html
new file mode 100644
index 00000000000..75dc8b98336
--- /dev/null
+++ b/files/web/dist/index.html
@@ -0,0 +1,60 @@
+
+
+ fheroes2
+
+
+
+
+
+
+
+
diff --git a/files/web/readme b/files/web/readme
new file mode 100644
index 00000000000..deeb60a7bc8
--- /dev/null
+++ b/files/web/readme
@@ -0,0 +1,14 @@
+building with pthread:
+1. modify src/dist/Makefile.web to enable pthread support
+2. modify src/engine/thread.cpp to allow spawn threads
+3. use patch sdl2_mixer.py; PR and issue description https://github.com/emscripten-core/emscripten/pull/23094
+4. check how to enable SharedArrayBuffer, options: serve with custom headers, use service worker to intercept request, use custom browser flags
+
+docker run --rm -v $(pwd):/src emscripten/emsdk:3.1.74 \
+ sh -c \
+ 'cp -f files/web/sdl2_mixer.py /emsdk/upstream/emscripten/tools/ports/sdl2_mixer.py && apt update && apt install -y gettext && emmake make -f Makefile.web'
+
+building without pthread:
+docker run --rm -v $(pwd):/src emscripten/emsdk:3.1.74 \
+ sh -c \
+ 'apt update && apt install -y gettext && emmake make -f Makefile.web'
diff --git a/files/web/sdl2_mixer.py b/files/web/sdl2_mixer.py
new file mode 100644
index 00000000000..a4723fd6152
--- /dev/null
+++ b/files/web/sdl2_mixer.py
@@ -0,0 +1,126 @@
+# Copyright 2016 The Emscripten Authors. All rights reserved.
+# Emscripten is available under two separate licenses, the MIT license and the
+# University of Illinois/NCSA Open Source License. Both these licenses can be
+# found in the LICENSE file.
+
+import os
+
+TAG = 'release-2.8.0'
+HASH = '494ccd74540f74e717f7e4f1dc7f96398c0f4b1883ab00c4a76b0c7239bd2c185cb4358a35ef47819c49e7c14dac7c37b98a29c7b5237478121571f5e7ac4dfc'
+
+deps = ['sdl2']
+variants = {
+ 'sdl2_mixer-mp3': {'SDL2_MIXER_FORMATS': ["mp3"]},
+ 'sdl2_mixer-none': {'SDL2_MIXER_FORMATS': []},
+ 'sdl2_mixer-mp3-mt': {'SDL2_MIXER_FORMATS': ["mp3"], 'PTHREADS': 1},
+ 'sdl2_mixer-none-mt': {'SDL2_MIXER_FORMATS': [], 'PTHREADS': 1},
+}
+
+
+def needed(settings):
+ return settings.USE_SDL_MIXER == 2
+
+
+def get_lib_name(settings):
+ settings.SDL2_MIXER_FORMATS.sort()
+ formats = '-'.join(settings.SDL2_MIXER_FORMATS)
+
+ libname = 'libSDL2_mixer'
+ if formats != '':
+ libname += '-' + formats
+ if settings.PTHREADS:
+ libname += '-mt'
+ libname += '.a'
+
+ return libname
+
+
+def get(ports, settings, shared):
+ sdl_build = os.path.join(ports.get_build_dir(), 'sdl2')
+ assert os.path.exists(sdl_build), 'You must use SDL2 to use SDL2_mixer'
+ ports.fetch_project('sdl2_mixer', f'https://github.com/libsdl-org/SDL_mixer/archive/{TAG}.zip', sha512hash=HASH)
+ libname = get_lib_name(settings)
+
+ def create(final):
+ source_path = ports.get_dir('sdl2_mixer', 'SDL_mixer-' + TAG)
+ flags = [
+ '-sUSE_SDL=2',
+ '-O2',
+ '-DMUSIC_WAV',
+ ]
+
+ if "ogg" in settings.SDL2_MIXER_FORMATS:
+ flags += [
+ '-sUSE_VORBIS',
+ '-DMUSIC_OGG',
+ ]
+
+ if "mp3" in settings.SDL2_MIXER_FORMATS:
+ flags += [
+ '-sUSE_MPG123',
+ '-DMUSIC_MP3_MPG123',
+ ]
+
+ if "mod" in settings.SDL2_MIXER_FORMATS:
+ flags += [
+ '-sUSE_MODPLUG',
+ '-DMUSIC_MOD_MODPLUG',
+ ]
+
+ if "mid" in settings.SDL2_MIXER_FORMATS:
+ flags += [
+ '-DMUSIC_MID_TIMIDITY',
+ ]
+
+ if settings.PTHREADS:
+ flags.append('-pthread')
+
+ build_dir = ports.clear_project_build('sdl2_mixer')
+ include_path = os.path.join(source_path, 'include')
+ includes = [
+ include_path,
+ os.path.join(source_path, 'src'),
+ os.path.join(source_path, 'src', 'codecs')
+ ]
+ ports.build_port(
+ source_path,
+ final,
+ build_dir,
+ flags=flags,
+ exclude_files=[
+ 'playmus.c',
+ 'playwave.c',
+ 'main.c',
+ ],
+ exclude_dirs=[
+ 'native_midi',
+ 'external',
+ 'Xcode',
+ ],
+ includes=includes,
+ )
+
+ ports.install_headers(include_path, target='SDL2')
+
+ return [shared.cache.get_lib(libname, create, what='port')]
+
+
+def clear(ports, settings, shared):
+ shared.cache.erase_lib(get_lib_name(settings))
+
+
+def process_dependencies(settings):
+ settings.USE_SDL = 2
+ if "ogg" in settings.SDL2_MIXER_FORMATS:
+ deps.append('vorbis')
+ settings.USE_VORBIS = 1
+ if "mp3" in settings.SDL2_MIXER_FORMATS:
+ deps.append('mpg123')
+ settings.USE_MPG123 = 1
+ if "mod" in settings.SDL2_MIXER_FORMATS:
+ deps.append('libmodplug')
+ settings.USE_MODPLUG = 1
+
+
+def show():
+ return 'sdl2_mixer (-sUSE_SDL_MIXER=2 or --use-port=sdl2_mixer; zlib license)'
diff --git a/src/dist/Makefile b/src/dist/Makefile
index 4e1c76db630..b8d4dce37fb 100644
--- a/src/dist/Makefile
+++ b/src/dist/Makefile
@@ -159,7 +159,11 @@ ifndef FHEROES2_WITH_SYSTEM_SMACKER
$(MAKE) -C thirdparty/libsmacker CCFLAGS="$(CCFLAGS_TP)" CFLAGS="$(CFLAGS_TP)" CXXFLAGS="$(CXXFLAGS_TP)" CPPFLAGS="$(CPPFLAGS_TP)"
endif
$(MAKE) -C engine
+ifeq ($(PLATFORM),web)
+ $(MAKE) -C web
+else
$(MAKE) -C fheroes2
+endif
ifdef FHEROES2_WITH_TOOLS
$(MAKE) -C tools
endif
@@ -169,5 +173,9 @@ ifndef FHEROES2_WITH_SYSTEM_SMACKER
$(MAKE) -C thirdparty/libsmacker clean
endif
$(MAKE) -C engine clean
+ifeq ($(PLATFORM),web)
+ $(MAKE) -C web clean
+else
$(MAKE) -C fheroes2 clean
+endif
$(MAKE) -C tools clean
diff --git a/src/dist/Makefile.web b/src/dist/Makefile.web
new file mode 100644
index 00000000000..aea8d5329a4
--- /dev/null
+++ b/src/dist/Makefile.web
@@ -0,0 +1,40 @@
+###########################################################################
+# fheroes2: https://github.com/ihhub/fheroes2 #
+# Copyright (C) 2021 - 2024 #
+# #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program; if not, write to the #
+# Free Software Foundation, Inc., #
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
+###########################################################################
+
+CCFLAGS := $(filter-out -pthread,$(CCFLAGS)) --use-port=sdl2_mixer \
+ --use-port=sdl2 \
+ --use-port=zlib
+
+LDFLAGS := $(filter-out -pthread,$(LDFLAGS)) -sENVIRONMENT=web \
+ --preload-file ../../../files/data/resurrection.h2d@/files/data/resurrection.h2d \
+ --preload-file ../../../files/lang/@/files/lang/ \
+ --preload-file ../../../files/soundfonts/fheroes2.sf3@/files/soundfonts/fheroes2.sf3 \
+ --preload-file ../../../data/@/data/ \
+ -sSTACK_SIZE=262144 \
+ -sINITIAL_MEMORY=64mb \
+ -sENVIRONMENT=web \
+ -sASYNCIFY \
+ -sASYNCIFY_STACK_SIZE=20480 \
+ -lidbfs.js
+
+ifdef FHEROES2_WITH_DEBUG
+LDFLAGS := $(LDFLAGS) -gsource-map
+CCFLAGS := $(CCFLAGS) -gsource-map
+endif
diff --git a/src/dist/web/Makefile b/src/dist/web/Makefile
new file mode 100644
index 00000000000..f08c7ebdce2
--- /dev/null
+++ b/src/dist/web/Makefile
@@ -0,0 +1,55 @@
+###########################################################################
+# fheroes2: https://github.com/ihhub/fheroes2 #
+# Copyright (C) 2021 - 2024 #
+# #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program; if not, write to the #
+# Free Software Foundation, Inc., #
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
+###########################################################################
+
+DEPLIBS := ../engine/libengine.a
+CCFLAGS := $(CCFLAGS) -I../../engine
+
+ifndef FHEROES2_WITH_SYSTEM_SMACKER
+DEPLIBS := $(DEPLIBS) ../thirdparty/libsmacker/libsmacker.a
+CCFLAGS := $(CCFLAGS) -I../../thirdparty/libsmacker
+endif
+
+SOURCEROOT := ../../fheroes2
+SOURCEDIRS := $(filter %/,$(wildcard $(SOURCEROOT)/*/))
+SOURCES := $(wildcard $(SOURCEROOT)/*/*.cpp)
+
+VPATH := $(SOURCEDIRS)
+
+.PHONY: all clean
+
+all: fheroes2.js
+
+fheroes2.js: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) $(DEPLIBS)
+ xgettext -d fheroes2.js --language=JavaScript -F -k_ -k_n:1,2 -o fheroes2.pot $(sort $(SOURCES))
+ $(MAKE) -C ../../../files/lang
+ $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS)
+ rm -f *.d *.o *.tmp *.pot~ *.pot
+ mkdir -p dist
+ mv -f fheroes2.* dist
+ cp ../../../files/web/dist/* dist
+
+%.o: %.cpp
+ $(CXX) -c -MD $< $(addprefix -I, $(SOURCEDIRS)) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS)
+
+include $(wildcard *.d)
+
+clean:
+ rm -f *.d *.o fheroes2.js fheroes2.wasm fheroes2.wasm.* fheroes2.data fheroes2.pot fheroes2.pot~
+ rm -rf dist
diff --git a/src/engine/thread.cpp b/src/engine/thread.cpp
index 10f86736900..d1b85419443 100644
--- a/src/engine/thread.cpp
+++ b/src/engine/thread.cpp
@@ -29,13 +29,14 @@ namespace MultiThreading
{
if ( !_worker ) {
_runFlag = true;
+#if !defined( __EMSCRIPTEN__ ) // disable pthread for web target
_worker = std::make_unique( AsyncManager::_workerThread, this );
-
{
std::unique_lock lock( _mutex );
_masterNotification.wait( lock, [this] { return !_runFlag; } );
}
+#endif
}
}
diff --git a/src/fheroes2/game/fheroes2.cpp b/src/fheroes2/game/fheroes2.cpp
index 10c68533628..51af2b7e988 100644
--- a/src/fheroes2/game/fheroes2.cpp
+++ b/src/fheroes2/game/fheroes2.cpp
@@ -337,10 +337,11 @@ int main( int argc, char ** argv )
if ( conf.isShowIntro() ) {
fheroes2::showTeamInfo();
-
+#if !defined( __EMSCRIPTEN__ )
Video::ShowVideo( "NWCLOGO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END );
Video::ShowVideo( "CYLOGO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END );
Video::ShowVideo( "H2XINTRO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END );
+#endif
}
try {
diff --git a/src/fheroes2/game/game_hotkeys.cpp b/src/fheroes2/game/game_hotkeys.cpp
index be067217449..d86f21c2b35 100644
--- a/src/fheroes2/game/game_hotkeys.cpp
+++ b/src/fheroes2/game/game_hotkeys.cpp
@@ -51,6 +51,10 @@
#include "ui_dialog.h"
#include "ui_language.h"
+#ifdef __EMSCRIPTEN__
+#include
+#endif
+
namespace
{
struct HotKeyEventInfo
@@ -463,6 +467,9 @@ void Game::HotKeySave()
const std::string & data = getHotKeyFileContent();
file.write( data.data(), data.size() );
+#ifdef __EMSCRIPTEN__
+ EM_ASM(FS.syncfs(err => err && console.warn("Error saving:", err)));
+#endif
}
void Game::globalKeyDownEvent( const fheroes2::Key key, const int32_t modifier )
diff --git a/src/fheroes2/game/game_io.cpp b/src/fheroes2/game/game_io.cpp
index b4e9c0d7bb0..4293da6a0d4 100644
--- a/src/fheroes2/game/game_io.cpp
+++ b/src/fheroes2/game/game_io.cpp
@@ -47,6 +47,10 @@
#include "world.h"
#include "zzlib.h"
+#ifdef __EMSCRIPTEN__
+#include
+#endif
+
namespace
{
const std::string autoSaveName{ "AUTOSAVE" };
@@ -154,7 +158,9 @@ bool Game::Save( const std::string & filePath, const bool autoSave /* = false */
if ( !autoSave ) {
Game::SetLastSaveName( filePath );
}
-
+#ifdef __EMSCRIPTEN__
+ EM_ASM(FS.syncfs(err => err && console.warn("Error saving:", err)));
+#endif
return true;
}
diff --git a/src/fheroes2/system/settings.cpp b/src/fheroes2/system/settings.cpp
index 20d5e9636ed..b2447177968 100644
--- a/src/fheroes2/system/settings.cpp
+++ b/src/fheroes2/system/settings.cpp
@@ -47,6 +47,10 @@
#include "ui_language.h"
#include "version.h"
+#ifdef __EMSCRIPTEN__
+#include
+#endif
+
#define STRINGIFY( DEF ) #DEF
#define EXPANDDEF( DEF ) STRINGIFY( DEF )
@@ -358,7 +362,9 @@ bool Settings::Save( const std::string_view fileName ) const
const std::string & data = String();
file.write( data.data(), data.size() );
-
+#ifdef __EMSCRIPTEN__
+ EM_ASM( FS.syncfs( err = > err && console.warn( "Error saving:", err ) ) );
+#endif
return true;
}