Skip to content

Commit

Permalink
Add WASM version of the engine (#9406)
Browse files Browse the repository at this point in the history
  • Loading branch information
ololoken authored Jan 5, 2025
1 parent 33c6233 commit 076150e
Show file tree
Hide file tree
Showing 107 changed files with 541 additions and 31 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/make.yml
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,44 @@ jobs:
artifactErrorsFailBuild: true
prerelease: true
replacesArtifacts: true
make-emscripten:
name: Make (Emscripten)
runs-on: ubuntu-latest
container: emscripten/emsdk:3.1.74
timeout-minutes: 30
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install gettext p7zip-full
- name: Build
run: |
emmake make -f Makefile.emscripten -j "$(nproc)"
env:
FHEROES2_STRICT_COMPILATION: ON
- name: Create package
run: |
# Translations and H2D files are already included in fheroes2.data
7z a -bb1 -tzip -- fheroes2_emscripten.zip LICENSE changelog.txt fheroes2.data fheroes2.js fheroes2.wasm ./docs/README.txt ./files/emscripten/*
- uses: actions/upload-artifact@v4
if: ${{ github.event_name == 'pull_request' }}
with:
name: fheroes2_emscripten.zip
path: fheroes2_emscripten.zip
if-no-files-found: error
- uses: ncipollo/release-action@v1
if: ${{ github.event_name == 'push' }}
with:
artifacts: fheroes2_emscripten.zip
body: ${{ github.event.head_commit.message }}
token: ${{ secrets.GITHUB_TOKEN }}
name: Emscripten build (latest commit)
tag: fheroes2-emscripten-sdl2_dev
allowUpdates: true
artifactErrorsFailBuild: true
prerelease: true
replacesArtifacts: true
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ android/.idea
android/.gradle

# Automatically created Android assets
android/app/src/main/assets/files/
android/app/src/main/assets/

# Mac magic folders
.DS_Store

# Web build artifacts
*.wasm
*.wasm.map
fheroes2.js
fheroes2.data
src/dist/web/dist/*
43 changes: 43 additions & 0 deletions Makefile.emscripten
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
###########################################################################
# fheroes2: https://github.com/ihhub/fheroes2 #
# Copyright (C) 2025 #
# #
# 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)

.PHONY: all clean translations

all: fheroes2.js

fheroes2.js: translations
$(MAKE) -C src/dist PLATFORM=emscripten
cp src/dist/fheroes2-ems/fheroes2.data .
cp src/dist/fheroes2-ems/fheroes2.js .
cp src/dist/fheroes2-ems/fheroes2.wasm .

translations:
$(MAKE) -C files/lang

clean:
$(MAKE) -C src/dist clean
$(MAKE) -C files/lang clean
rm -f fheroes2.data fheroes2.js fheroes2.wasm
4 changes: 2 additions & 2 deletions Makefile.vita
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
###########################################################################
# fheroes2: https://github.com/ihhub/fheroes2 #
# Copyright (C) 2021 - 2024 #
# Copyright (C) 2021 - 2025 #
# #
# 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 #
Expand Down Expand Up @@ -40,7 +40,7 @@ fheroes2.vpk: eboot.bin param.sfo translations
--add files/images/platform/psv/sce_sys/livearea/contents/template.xml=sce_sys/livearea/contents/template.xml \
--add files/data=files/data \
--add files/lang/vita_temp=files/lang \
fheroes2.vpk
fheroes2.vpk
rm -r files/lang/vita_temp

translations: fheroes2.elf
Expand Down
10 changes: 9 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ tasks.register('copyH2D', Copy) {
into 'src/main/assets/files/data'
}

tasks.register('copyTimidity', Copy) {
from('../../files/timidity') {
include 'instruments/*.pat'
include 'timidity.cfg'
}
into 'src/main/assets'
}

tasks.register('copyTranslations', Copy) {
from('../../files/lang') {
include '*.mo'
Expand All @@ -107,5 +115,5 @@ tasks.register('copyTranslations', Copy) {
}

preBuild {
dependsOn copyH2D, copyTranslations
dependsOn copyH2D, copyTimidity, copyTranslations
}
Binary file added files/emscripten/fheroes2.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
210 changes: 210 additions & 0 deletions files/emscripten/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<html lang="en">
<head>
<title>fheroes2</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/progress.js/0.1.0/progress.min.js" integrity="sha512-CklrzCqwODBOEJHJq73SZrgWC7xcxssgg5M1xokosfDDz2/nLTCuMDyc51gbJtb8DriV4EYjJSlklPH5Ejn9XA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/progress.js/0.1.0/progressjs.min.css" integrity="sha512-ovRFHsWpnYUBNZd/8CmbaWAKBDO8xb0l5Y8PdIae1O5RXO2QyU3CZGNzuYuE0CQHKazIzWMQpnTn26WpPjexfw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
body { margin: 0; height: 0; font-family: "Roboto", serif;}
#canvas {
height: 100vh;
width: 100vw;
display: block;
margin: 0 auto;
background: url(./fheroes2.jpeg) center center;
background-size: cover;
position: absolute;
z-index: 10;
}
#uploader, #launcher {
z-index: 100;
position: absolute;
color: #fff;
font-size: 30px;
text-stroke: 1px #000;
-webkit-text-stroke: 1px #000;
height: 100vh;
width: 100vw;
}
#launcher {
display: none;
text-stroke: 1px #fff;
-webkit-text-stroke: 1px #fff;
}
#uploader input[type=file] {
display: none
}
#uploader label {
text-align: center;
display: block;
cursor: pointer;
height: 100vh;
width: 100vw;
position: absolute;
}
#uploader label span {
padding: 10vh 0 0 0;
display: block;
}
#launcher button {
font-size: 30px;
}
#launcher .buttons {
text-align: center;
padding: 10vh 0 0 0;
}
</style>
</head>
<body>
<canvas id="canvas" width="640" height="400"></canvas>
<div id="uploader">
<label>
<span>Click to select your Heroes 2 folder</span>
<input
type="file"
directory="directory"
multiple
webkitdirectory="webkitdirectory"
id="files"
/>
</label>
</div>
<div id="launcher">
<div class="buttons">
<button onclick="Module.startGame()">Start game</button>
<button onclick="Module.deleteFiles()">Delete files</button>
</div>
</div>
<script>
const S_IFMT = 61440; /* 0x0170000 type of file */
const S_IFDIR = 16384; /* 0x0040000 directory */
const progressBar = progressJs("#canvas").setOptions({
theme: 'blueOverlayRadiusWithPercentBar',
overlayMode: true,
considerTransition: true
});
const canvas = document.querySelector('canvas');
canvas.oncontextmenu = event => event.preventDefault();
Object.assign(window, { Module: {
preRun: [
() => Object.assign(window.ENV, {
FHEROES2_DATA: `/fheroes2/data`,
HOME: '/fheroes2'
}),
() => {
addRunDependency('syncfs');
FS.mkdir('/fheroes2');
FS.mount(IDBFS, { root: '/' }, '/fheroes2');
FS.syncfs(true, (err) => {
if (err) throw err;
try {
if (Object.keys(FS.lookupPath(`${ENV.FHEROES2_DATA}`)).length === 0) return;
document.querySelector('#uploader').style.display = 'none';
document.querySelector('#launcher').style.display = 'block';
}
catch (ignore) {
FS.mkdir(`${ENV.FHEROES2_DATA}`);
}
});
},
() => {
document.querySelector('input[type=file]').addEventListener('input', e => {
const files = [...e.target.files];
const requiredFiles = ['data/HEROES2.AGG', 'data/HEROES2X.AGG']
if (files.length === 1) return Module.clearInput('zip archives are not supported yet, sorry');
if (files.length > 3000) return Module.clearInput('wrong directory, or your maps collection is really awesome.');
const requiredResolved = files.filter(({ webkitRelativePath: path }) =>
requiredFiles.some(rq => path.toLocaleLowerCase().endsWith(rq.toLocaleLowerCase()))).length === 2;
if (!requiredResolved) return Module.clearInput('wrong directory');
const whitListed = ['data', 'heroes2', 'maps', 'music'];
files.filter(file => {
const [fileName, ...path] = file.webkitRelativePath.split('/').reverse();
if (fileName.startsWith('.')) return false;
return path
.map(folder => folder.toLocaleLowerCase())
.some(folder => whitListed.includes(folder))
}).reduce((uploaded, file, _ignore, queue) => {
const [fileName, ...path] = file.webkitRelativePath.split('/').reverse();
const [ignore, ...relativePath] = path.reverse();
Module.mkdirWithParents(`${ENV.FHEROES2_DATA}/${relativePath.join('/')}`);

const reader = new FileReader();
reader.addEventListener('error', console.error);
reader.addEventListener('loadend', ({ target: { result } }) => {
FS.writeFile(`${ENV.FHEROES2_DATA}/${relativePath.join('/')}/${fileName}`, new Uint8Array(result), {
encoding: 'binary'
});
uploaded.push(file);
if (uploaded.length === queue.length) {
FS.syncfs(false, err => {
if (err) return Module.clearInput('Failed to sync FS');
document.querySelector('#uploader').style.display = 'none';
document.querySelector('#launcher').style.display = 'block';
})
}
});
reader.readAsArrayBuffer(file);
return uploaded;
}, []);
})
}
],
clearInput: (msg = '') => {
document.querySelector('input[type=file]').value = null;
msg && alert(msg);
},
mkdirWithParents: path => {
const parts = path.split('/');
try { FS.lookupPath(parts.join('/')) }
catch (ignore) {
parts.reduce((p, part) => {
p = [...p, part];
try { FS.lookupPath(p.join('/')) }
catch (ignore) { FS.mkdir(p.join('/')) }
return p;
}, []);
}
},
clearPath: path => {
try {
Object.keys(FS.lookupPath(path).node.contents).forEach(node =>
(FS.stat(`${path}/${node}`).mode & S_IFMT) === S_IFDIR
? Module.clearPath(`${path}/${node}`)
: FS.unlink(`${path}/${node}`));
FS.rmdir(`${path}`)
} catch (ignore) {}
},
startGame: () => {
removeRunDependency('syncfs');
document.querySelector('#uploader').style.display = 'none';
document.querySelector('#launcher').style.display = 'none';
},
deleteFiles: () => {
if (false === confirm('Are you sure?')) return;
Module.clearPath(ENV.FHEROES2_DATA);
FS.syncfs(false, ignore => {});
Module.clearInput();
document.querySelector('#uploader').style.display = 'block';
document.querySelector('#launcher').style.display = 'none';
},
setStatus: status => {
const dlProgressRE = /(?<progress>\d+)\/(?<total>\d+)/ig;
if (!status || !dlProgressRE.test(status)) {
if ((status ?? '').startsWith('Downloading data')) progressBar.start();
return;
}
dlProgressRE.lastIndex = 0;
const {groups: {progress, total}} = [...status.matchAll(dlProgressRE)][0];
const done = Math.round(progress / total * 100);
progressBar.set(done);
if (100 === done) progressBar.end();
},
canvas
}
});

document.head.append(Object.assign(document.createElement('script'), {src: './fheroes2.js'}));
</script>
</body>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 9 additions & 1 deletion src/dist/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
###########################################################################
# fheroes2: https://github.com/ihhub/fheroes2 #
# Copyright (C) 2021 - 2024 #
# Copyright (C) 2021 - 2025 #
# #
# 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 #
Expand Down Expand Up @@ -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),emscripten)
$(MAKE) -C fheroes2-ems
else
$(MAKE) -C fheroes2
endif
ifdef FHEROES2_WITH_TOOLS
$(MAKE) -C tools
endif
Expand All @@ -169,5 +173,9 @@ ifndef FHEROES2_WITH_SYSTEM_SMACKER
$(MAKE) -C thirdparty/libsmacker clean
endif
$(MAKE) -C engine clean
ifeq ($(PLATFORM),emscripten)
$(MAKE) -C fheroes2-ems clean
else
$(MAKE) -C fheroes2 clean
endif
$(MAKE) -C tools clean
Loading

0 comments on commit 076150e

Please sign in to comment.