Merge branch 'broken_shit' into 'master'
Broken shit See merge request fainsil/booplaysgba!1
This commit is contained in:
commit
6940e6acb5
|
@ -8,5 +8,5 @@ charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.{yaml,yml,toml}]
|
[*.{yaml,yml,toml,html,svelte}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
6
.env
Normal file
6
.env
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
WEBSOCKET_LISTEN=0.0.0.0
|
||||||
|
WEBSOCKET_PORT=6789
|
||||||
|
RTMP_HOST=rtmp
|
||||||
|
RTMP_PORT=1935
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -6,14 +6,16 @@
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/src/server.py",
|
"program": "${workspaceFolder}/src/server.py",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal",
|
||||||
|
"envFile": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Emulator",
|
"name": "Emulator",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/src/emulator.py",
|
"program": "${workspaceFolder}/src/emulator.py",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal",
|
||||||
|
"envFile": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -13,6 +13,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"python.analysis.extraPaths": [
|
"python.analysis.extraPaths": [
|
||||||
"./mgba/build/python/lib.linux-x86_64-3.9"
|
"./mgba/src/platform/python/build/lib.linux-x86_64-3.9/mgba"
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
51
Dockerfile
Normal file
51
Dockerfile
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
# set /code as the work directory
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
# update alpine repositories
|
||||||
|
apk update \
|
||||||
|
# build tools dependencies
|
||||||
|
&& apk add build-base cmake git \
|
||||||
|
# mgba dependencies
|
||||||
|
&& apk add libffi-dev elfutils-dev libzip-tools minizip-dev libedit-dev sqlite-dev libepoxy-dev ffmpeg ffmpeg-dev libpng-dev jpeg-dev \
|
||||||
|
# install poetry and cffi deps for mgba
|
||||||
|
&& pip install poetry cffi
|
||||||
|
|
||||||
|
# copy poetry config files
|
||||||
|
COPY ./pyproject.toml /code
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
cd /code \
|
||||||
|
# clone mgba
|
||||||
|
&& git clone https://github.com/mgba-emu/mgba.git mgba \
|
||||||
|
# create build directory
|
||||||
|
&& mkdir mgba/build \
|
||||||
|
# go to the build directory
|
||||||
|
&& cd mgba/build \
|
||||||
|
# configure the build
|
||||||
|
&& cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local .. \
|
||||||
|
# build mGBA
|
||||||
|
&& make \
|
||||||
|
# install mGBA
|
||||||
|
&& make install
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
cd /code/mgba/src/platform/python \
|
||||||
|
# install mGBA bindings
|
||||||
|
&& BINDIR=/code/mgba/build/include LIBDIR=/code/mgba/build/include python setup.py install
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
# go to the workdir
|
||||||
|
cd /code/ \
|
||||||
|
# # config poetry to not create a .venv
|
||||||
|
&& poetry config virtualenvs.create false \
|
||||||
|
# # upgrade pip
|
||||||
|
&& poetry run pip install --upgrade pip \
|
||||||
|
# install poetry
|
||||||
|
&& poetry install --no-interaction --no-ansi --no-dev
|
||||||
|
|
||||||
|
# copy the src files
|
||||||
|
COPY ./src /code/src
|
||||||
|
COPY ./roms/pokemon.gba /code/roms/pokemon.gba
|
|
@ -35,14 +35,15 @@ Build the mGBA python bindings:
|
||||||
cd booplaysgba
|
cd booplaysgba
|
||||||
mkdir mgba/build
|
mkdir mgba/build
|
||||||
cd mgba/build
|
cd mgba/build
|
||||||
cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF ..
|
cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF ..
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the dependencies :
|
Install the dependencies :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry install
|
poetry run pip install --upgrade pip
|
||||||
|
BINDIR=`pwd`/mgba/build/ LIBDIR=`pwd`/mgba/build/ poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
41
docker-compose.yml
Normal file
41
docker-compose.yml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
entrypoint: poetry run python src/server.py
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- REDIS_HOST=$REDIS_HOST
|
||||||
|
- REDIS_PORT=$REDIS_PORT
|
||||||
|
- WEBSOCKET_LISTEN=$WEBSOCKET_LISTEN
|
||||||
|
- WEBSOCKET_PORT=$WEBSOCKET_PORT
|
||||||
|
ports:
|
||||||
|
- $WEBSOCKET_PORT:$WEBSOCKET_PORT
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
emulator:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
entrypoint: poetry run python src/emulator.py
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- REDIS_HOST=$REDIS_HOST
|
||||||
|
- REDIS_PORT=$REDIS_PORT
|
||||||
|
- RTMP_HOST=$RTMP_HOST
|
||||||
|
- RTMP_PORT=$RTMP_PORT
|
||||||
|
depends_on:
|
||||||
|
- rtmp
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- ./states/:/code/states/
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- $REDIS_PORT:6379
|
||||||
|
rtmp:
|
||||||
|
image: tiangolo/nginx-rtmp
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- $RTMP_PORT:1935
|
48
memo.full.sh
Normal file
48
memo.full.sh
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
0 apk update
|
||||||
|
1 apk add build-base cmake git
|
||||||
|
2 apk add libffi-dev elfutils-dev libzip-tools minizip-dev libedit-dev sqlite-dev libepoxy-dev ffmpeg-dev libpng-dev
|
||||||
|
3 pip install poetry cffi
|
||||||
|
4 mkdir /code
|
||||||
|
5 cd /code
|
||||||
|
6 git clone https://github.com/mgba-emu/mgba.git mgba
|
||||||
|
7 mkdir mgba/build
|
||||||
|
8 cd mgba/build
|
||||||
|
9 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF ..
|
||||||
|
10 make
|
||||||
|
11 apk add vim
|
||||||
|
12 vim
|
||||||
|
13 vim pyproject.tom
|
||||||
|
14 vim pyproject.toml
|
||||||
|
15 cd /code
|
||||||
|
16 poetry config virtualenvs.create false
|
||||||
|
17 poetry run pip install --upgrade pip
|
||||||
|
18 vim pyproject.toml
|
||||||
|
19 poetry run pip install --upgrade pip
|
||||||
|
20 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ poetry install --no-interaction --no-ansi --no-dev
|
||||||
|
21 which python
|
||||||
|
22 ls /usr/local
|
||||||
|
23 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local
|
||||||
|
24 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local ..
|
||||||
|
25 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/ ..
|
||||||
|
26 cd mgba/build
|
||||||
|
27 cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/ ..
|
||||||
|
28 make
|
||||||
|
29 cd /code/
|
||||||
|
30 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ poetry install
|
||||||
|
31 poetry install
|
||||||
|
32 cd mgba/src/platform/python/
|
||||||
|
33 python setup.py install
|
||||||
|
34 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ python setup.py install
|
||||||
|
35 BINDIR=/code/mgba/build/ LIBDIR=/code/mgba/build/ python setup.py installfind
|
||||||
|
36 find
|
||||||
|
37 find / | grep flags.h
|
||||||
|
38 BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py installfind
|
||||||
|
39 BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py install
|
||||||
|
40 cd ../../..
|
||||||
|
41 cd build
|
||||||
|
42 make install
|
||||||
|
43 mgba
|
||||||
|
44 cd ..
|
||||||
|
45 cd src/platform/python/
|
||||||
|
46 BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py install
|
||||||
|
47 history
|
17
memo.sh
Normal file
17
memo.sh
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
apk update
|
||||||
|
apk add build-base cmake git vim
|
||||||
|
apk add libffi-dev elfutils-dev libzip-tools minizip-dev libedit-dev sqlite-dev libepoxy-dev ffmpeg-dev libpng-dev jpeg-dev
|
||||||
|
pip install poetry cffi
|
||||||
|
mkdir /code
|
||||||
|
cd /code
|
||||||
|
vim pyproject.toml # copy le pyproject.tom manuel
|
||||||
|
git clone https://github.com/mgba-emu/mgba.git mgba
|
||||||
|
mkdir mgba/build
|
||||||
|
cd mgba/build
|
||||||
|
cmake -DBUILD_PYTHON=ON -DBUILD_QT=OFF -DBUILD_SDL=OFF -DUSE_DISCORD_RPC=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr/local/ ..
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
cd /code/mgba/src/platform/python/
|
||||||
|
BINDIR=/code/mgba/build/include/ LIBDIR=/code/mgba/build/include/ python setup.py install
|
||||||
|
cd /code/
|
||||||
|
poetry install
|
42
poetry.lock
generated
42
poetry.lock
generated
|
@ -176,7 +176,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "2.3.3"
|
version = "2.3.4"
|
||||||
description = "File identification library for Python"
|
description = "File identification library for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -207,6 +207,27 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mgba"
|
||||||
|
version = "0.10.0.7138+g59cb5c189"
|
||||||
|
description = ""
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
develop = false
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cached-property = "*"
|
||||||
|
cffi = ">=1.6"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cinema = ["pytest"]
|
||||||
|
pil = ["Pillow (>=2.3)"]
|
||||||
|
|
||||||
|
[package.source]
|
||||||
|
type = "directory"
|
||||||
|
url = "mgba/src/platform/python"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "0.910"
|
version = "0.910"
|
||||||
|
@ -250,7 +271,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pbr"
|
name = "pbr"
|
||||||
version = "5.6.0"
|
version = "5.7.0"
|
||||||
description = "Python Build Reasonableness"
|
description = "Python Build Reasonableness"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -302,7 +323,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.20"
|
version = "2.21"
|
||||||
description = "C parser in Python"
|
description = "C parser in Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -454,7 +475,7 @@ python-versions = ">=3.7"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "23107f337b52d511f360f86746a69cbeec745e69d23beb1051a2fb9c5c303fd9"
|
content-hash = "06c8f160e3bf91f78480ff21a5e205147f1a23c38a405232ab8252e36cc7809f"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
asyncio = [
|
asyncio = [
|
||||||
|
@ -568,8 +589,8 @@ gitpython = [
|
||||||
{file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"},
|
{file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"},
|
||||||
]
|
]
|
||||||
identify = [
|
identify = [
|
||||||
{file = "identify-2.3.3-py2.py3-none-any.whl", hash = "sha256:ffab539d9121b386ffdea84628ff3eefda15f520f392ce11b393b0a909632cdf"},
|
{file = "identify-2.3.4-py2.py3-none-any.whl", hash = "sha256:4de55a93e0ba72bf917c840b3794eb1055a67272a1732351c557c88ec42011b1"},
|
||||||
{file = "identify-2.3.3.tar.gz", hash = "sha256:b9ffbeb7ed87e96ce017c66b80ca04fda3adbceb5c74e54fc7d99281d27d0859"},
|
{file = "identify-2.3.4.tar.gz", hash = "sha256:595283a1c3a078ac5774ad4dc4d1bdd0c1602f60bcf11ae673b64cb2b1945762"},
|
||||||
]
|
]
|
||||||
isort = [
|
isort = [
|
||||||
{file = "isort-5.10.0-py3-none-any.whl", hash = "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f"},
|
{file = "isort-5.10.0-py3-none-any.whl", hash = "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f"},
|
||||||
|
@ -579,6 +600,7 @@ mccabe = [
|
||||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||||
]
|
]
|
||||||
|
mgba = []
|
||||||
mypy = [
|
mypy = [
|
||||||
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
||||||
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
||||||
|
@ -617,8 +639,8 @@ pathspec = [
|
||||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||||
]
|
]
|
||||||
pbr = [
|
pbr = [
|
||||||
{file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"},
|
{file = "pbr-5.7.0-py2.py3-none-any.whl", hash = "sha256:60002958e459b195e8dbe61bf22bcf344eedf1b4e03a321a5414feb15566100c"},
|
||||||
{file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"},
|
{file = "pbr-5.7.0.tar.gz", hash = "sha256:4651ca1445e80f2781827305de3d76b3ce53195f2227762684eb08f17bc473b7"},
|
||||||
]
|
]
|
||||||
pillow = [
|
pillow = [
|
||||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||||
|
@ -676,8 +698,8 @@ pycodestyle = [
|
||||||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||||
]
|
]
|
||||||
pycparser = [
|
pycparser = [
|
||||||
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
|
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||||
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
|
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||||
]
|
]
|
||||||
pydocstyle = [
|
pydocstyle = [
|
||||||
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
|
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
|
||||||
|
|
|
@ -4,9 +4,7 @@ version = "0.1.0"
|
||||||
description = "Émulateur collaboratif pour patienter dans le B00"
|
description = "Émulateur collaboratif pour patienter dans le B00"
|
||||||
authors = ["Laureηt <laurentfainsin@protonmail.com>"]
|
authors = ["Laureηt <laurentfainsin@protonmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
packages = [
|
|
||||||
{ include = "mgba", from = "mgba/build/python/lib.linux-x86_64-3.9" }
|
|
||||||
]
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
asyncio = "^3.4.3"
|
asyncio = "^3.4.3"
|
||||||
|
@ -15,6 +13,7 @@ cffi = "^1.15.0"
|
||||||
cached-property = "^1.5.2"
|
cached-property = "^1.5.2"
|
||||||
Pillow = "^8.4.0"
|
Pillow = "^8.4.0"
|
||||||
redis = "^3.5.3"
|
redis = "^3.5.3"
|
||||||
|
mgba = { path="mgba/src/platform/python" }
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^21.9b0"
|
black = "^21.9b0"
|
||||||
|
|
89
src/Controller.svelte
Normal file
89
src/Controller.svelte
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<script lang="ts">
|
||||||
|
const websocket: WebSocket = new WebSocket("ws://localhost:6789/");
|
||||||
|
|
||||||
|
const sendAction = (key: string) => () => {
|
||||||
|
websocket.send(JSON.stringify({ action: key }));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table class="buttons">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding-bottom: 1rem;">
|
||||||
|
<button id="l" on:click={sendAction("l")}>L</button>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
<td colspan="2" style="padding-bottom: 1rem; text-align: right;">
|
||||||
|
<button id="r" on:click={sendAction("r")}>R</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td />
|
||||||
|
<td>
|
||||||
|
<button id="up" on:click={sendAction("up")}> ∧ </button>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
<td>
|
||||||
|
<button id="select" on:click={sendAction("select")}> select </button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button id="start" on:click={sendAction("start")}> start </button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<button id="left" on:click={sendAction("left")}> < </button>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
<td style="padding-right: 3rem;">
|
||||||
|
<button id="right" on:click={sendAction("right")}> > </button>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
<td />
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td />
|
||||||
|
<td>
|
||||||
|
<button id="down" on:click={sendAction("down")}> ∨ </button>
|
||||||
|
</td>
|
||||||
|
<td />
|
||||||
|
<td>
|
||||||
|
<button id="a" on:click={sendAction("a")}>A</button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button id="b" on:click={sendAction("b")}>B</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#a,
|
||||||
|
#b {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left,
|
||||||
|
#right,
|
||||||
|
#up,
|
||||||
|
#down {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#start,
|
||||||
|
#select,
|
||||||
|
#l,
|
||||||
|
#r {
|
||||||
|
width: 3.5rem;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
</style>
|
114
src/admin.html
114
src/admin.html
|
@ -2,78 +2,78 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Admin</title>
|
<title>Admin</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dashboard {
|
#dashboard {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="login">
|
<div id="login">
|
||||||
<input type="password" id="password-text">
|
<input type="password" id="password-text">
|
||||||
<input type="button" id="password-button" value="Login" onclick="sendCreds();">
|
<input type="button" id="password-button" value="Login" onclick="sendCreds();">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="dashboard">
|
<div id="dashboard">
|
||||||
<ul id="stateList"></ul>
|
<ul id="stateList"></ul>
|
||||||
<button id="save">save</button>
|
<button id="save">save</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||||
var passwordInput = document.querySelector('#password-text');
|
var passwordInput = document.querySelector('#password-text');
|
||||||
|
|
||||||
var divLogin = document.querySelector("#login");
|
var divLogin = document.querySelector("#login");
|
||||||
var divDashboard = document.querySelector("#dashboard");
|
var divDashboard = document.querySelector("#dashboard");
|
||||||
var stateList = document.querySelector("#stateList");
|
var stateList = document.querySelector("#stateList");
|
||||||
var saveButton = document.getElementById('save');
|
var saveButton = document.getElementById('save');
|
||||||
|
|
||||||
saveButton.onclick = function (event) {
|
saveButton.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ 'admin': 'save' }));
|
websocket.send(JSON.stringify({ 'admin': 'save' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveStates(ev) {
|
function receiveStates(ev) {
|
||||||
let msg = JSON.parse(ev.data);
|
let msg = JSON.parse(ev.data);
|
||||||
let states = msg.state
|
let states = msg.state
|
||||||
for (var i = 0; i < states.length; i++) {
|
for (var i = 0; i < states.length; i++) {
|
||||||
var state = states[i];
|
var state = states[i];
|
||||||
var li = document.createElement('li');
|
var li = document.createElement('li');
|
||||||
li.appendChild(document.createTextNode(state));
|
li.appendChild(document.createTextNode(state));
|
||||||
stateList.appendChild(li);
|
stateList.appendChild(li);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function authSuccess(ev) {
|
function authSuccess(ev) {
|
||||||
let msg = JSON.parse(ev.data);
|
let msg = JSON.parse(ev.data);
|
||||||
if (msg.auth === "success") {
|
if (msg.auth === "success") {
|
||||||
divLogin.style.display = "none";
|
divLogin.style.display = "none";
|
||||||
divDashboard.style.display = "unset";
|
divDashboard.style.display = "unset";
|
||||||
websocket.removeEventListener('message', authSuccess);
|
websocket.removeEventListener('message', authSuccess);
|
||||||
websocket.send(JSON.stringify({ "state": "get" }));
|
websocket.send(JSON.stringify({ "state": "get" }));
|
||||||
websocket.addEventListener('message', receiveStates);
|
websocket.addEventListener('message', receiveStates);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function sendCreds() {
|
function sendCreds() {
|
||||||
var message = JSON.stringify({ "auth": passwordInput.value });
|
var message = JSON.stringify({ "auth": passwordInput.value });
|
||||||
websocket.send(message)
|
websocket.send(message)
|
||||||
websocket.addEventListener('message', authSuccess);
|
websocket.addEventListener('message', authSuccess);
|
||||||
};
|
};
|
||||||
|
|
||||||
passwordInput.addEventListener("keydown", function (e) {
|
passwordInput.addEventListener("keydown", function (e) {
|
||||||
if (e.key === "Enter") { sendCreds(); }
|
if (e.key === "Enter") { sendCreds(); }
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
63
src/arrow.svg
Normal file
63
src/arrow.svg
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="8.0677929mm"
|
||||||
|
height="9.2203341mm"
|
||||||
|
viewBox="0 0 8.0677928 9.2203341"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="arrow.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="13.108739"
|
||||||
|
inkscape:cx="6.5986514"
|
||||||
|
inkscape:cy="-6.7512214"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1054"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-93.682808,-119.4171)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||||
|
x="91.903885"
|
||||||
|
y="129.03831"
|
||||||
|
id="text49897"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan49895"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.6389px;font-family:Inter;-inkscape-font-specification:'Inter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583"
|
||||||
|
x="91.903885"
|
||||||
|
y="129.03831">></tspan></text>
|
||||||
|
<g
|
||||||
|
aria-label=">"
|
||||||
|
id="text51577"
|
||||||
|
style="font-size:10.5833px;line-height:1.25;stroke-width:0.264583">
|
||||||
|
<path
|
||||||
|
d="m 101.7506,124.52837 -8.067792,4.10906 v -1.60354 l 6.188649,-2.98157 -0.05011,0.10022 v -0.25055 l 0.05011,0.10022 -6.188649,-2.98158 v -1.60353 l 8.067792,4.10906 z"
|
||||||
|
style="font-size:17.6389px;font-family:Inter;-inkscape-font-specification:'Inter, Normal'"
|
||||||
|
id="path51587" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
216
src/client.html
216
src/client.html
|
@ -2,127 +2,127 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Telecommande</title>
|
<title>Telecommande</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#a,
|
#a,
|
||||||
#b {
|
#b {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#left,
|
#left,
|
||||||
#right,
|
#right,
|
||||||
#up,
|
#up,
|
||||||
#down {
|
#down {
|
||||||
border-radius: 10%;
|
border-radius: 10%;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#start,
|
#start,
|
||||||
#select,
|
#select,
|
||||||
#l,
|
#l,
|
||||||
#r {
|
#r {
|
||||||
border-radius: 25%;
|
border-radius: 25%;
|
||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<table class="buttons">
|
<table class="buttons">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" style="padding-bottom: 1rem;"><button id="l">L</button></td>
|
<td colspan="2" style="padding-bottom: 1rem;"><button id="l">L</button></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td colspan="2" style="padding-bottom: 1rem; text-align: right;"><button id="r">R</button></td>
|
<td colspan="2" style="padding-bottom: 1rem; text-align: right;"><button id="r">R</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><button id="up"> ∧ </button></td>
|
<td><button id="up"> ∧ </button></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><button id="select">select</button></td>
|
<td><button id="select">select</button></td>
|
||||||
<td><button id="start">start</button></td>
|
<td><button id="start">start</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><button id="left">
|
<td><button id="left">
|
||||||
< </button>
|
< </button>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td style="padding-right: 3rem;"><button id="right"> > </button></td>
|
<td style="padding-right: 3rem;"><button id="right"> > </button></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><button id="down"> ∨ </button></td>
|
<td><button id="down"> ∨ </button></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><button id="a">A</button></td>
|
<td><button id="a">A</button></td>
|
||||||
<td><button id="b">B</button></td>
|
<td><button id="b">B</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
var websocket = new WebSocket("ws://localhost:6789/");
|
||||||
|
|
||||||
var a = document.getElementById('a'),
|
var a = document.getElementById('a'),
|
||||||
b = document.getElementById('b'),
|
b = document.getElementById('b'),
|
||||||
select = document.getElementById('select'),
|
select = document.getElementById('select'),
|
||||||
start = document.getElementById('start'),
|
start = document.getElementById('start'),
|
||||||
right = document.getElementById('right'),
|
right = document.getElementById('right'),
|
||||||
left = document.getElementById('left'),
|
left = document.getElementById('left'),
|
||||||
up = document.getElementById('up'),
|
up = document.getElementById('up'),
|
||||||
down = document.getElementById('down'),
|
down = document.getElementById('down'),
|
||||||
r = document.getElementById('r'),
|
r = document.getElementById('r'),
|
||||||
l = document.getElementById('l');
|
l = document.getElementById('l');
|
||||||
|
|
||||||
a.onclick = function (event) {
|
a.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'a' }));
|
websocket.send(JSON.stringify({ action: 'a' }));
|
||||||
}
|
}
|
||||||
b.onclick = function (event) {
|
b.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'b' }));
|
websocket.send(JSON.stringify({ action: 'b' }));
|
||||||
}
|
}
|
||||||
select.onclick = function (event) {
|
select.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'select' }));
|
websocket.send(JSON.stringify({ action: 'select' }));
|
||||||
}
|
}
|
||||||
start.onclick = function (event) {
|
start.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'start' }));
|
websocket.send(JSON.stringify({ action: 'start' }));
|
||||||
}
|
}
|
||||||
right.onclick = function (event) {
|
right.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'right' }));
|
websocket.send(JSON.stringify({ action: 'right' }));
|
||||||
}
|
}
|
||||||
left.onclick = function (event) {
|
left.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'left' }));
|
websocket.send(JSON.stringify({ action: 'left' }));
|
||||||
}
|
}
|
||||||
up.onclick = function (event) {
|
up.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'up' }));
|
websocket.send(JSON.stringify({ action: 'up' }));
|
||||||
}
|
}
|
||||||
down.onclick = function (event) {
|
down.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'down' }));
|
websocket.send(JSON.stringify({ action: 'down' }));
|
||||||
}
|
}
|
||||||
r.onclick = function (event) {
|
r.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'r' }));
|
websocket.send(JSON.stringify({ action: 'r' }));
|
||||||
}
|
}
|
||||||
l.onclick = function (event) {
|
l.onclick = function (event) {
|
||||||
websocket.send(JSON.stringify({ action: 'l' }));
|
websocket.send(JSON.stringify({ action: 'l' }));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
105
src/emulator.py
105
src/emulator.py
|
@ -1,4 +1,6 @@
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
from subprocess import PIPE, Popen # nosec
|
from subprocess import PIPE, Popen # nosec
|
||||||
|
|
||||||
|
@ -7,17 +9,36 @@ import mgba.image
|
||||||
import mgba.log
|
import mgba.log
|
||||||
import redis
|
import redis
|
||||||
|
|
||||||
from settings import FPS, HEIGHT, KEYS, MGBA_KEYS, POLLING_RATE, REDIS_INIT, SPF, WIDTH
|
from settings import (
|
||||||
|
EMULATOR_FPS,
|
||||||
|
EMULATOR_HEIGHT,
|
||||||
|
EMULATOR_POLLING_RATE,
|
||||||
|
EMULATOR_ROM_PATH,
|
||||||
|
EMULATOR_SPF,
|
||||||
|
EMULATOR_WIDTH,
|
||||||
|
FFMPEG_BITRATE,
|
||||||
|
FFMPEG_FPS,
|
||||||
|
FFMPEG_HEIGHT,
|
||||||
|
FFMPEG_WIDTH,
|
||||||
|
KEYS_ID,
|
||||||
|
KEYS_MGBA,
|
||||||
|
KEYS_RESET,
|
||||||
|
REDIS_HOST,
|
||||||
|
REDIS_PORT,
|
||||||
|
RTMP_STREAM_URI,
|
||||||
|
)
|
||||||
|
from utils import States
|
||||||
|
|
||||||
core = mgba.core.load_path("roms/pokemon.gba")
|
core = mgba.core.load_path(EMULATOR_ROM_PATH)
|
||||||
# core = mgba.core.load_path("roms/BtnTest.gba")
|
screen = mgba.image.Image(EMULATOR_WIDTH, EMULATOR_HEIGHT)
|
||||||
screen = mgba.image.Image(WIDTH, HEIGHT)
|
|
||||||
core.set_video_buffer(screen)
|
core.set_video_buffer(screen)
|
||||||
core.reset()
|
core.reset()
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
mgba.log.silence()
|
mgba.log.silence()
|
||||||
r = redis.Redis(host="localhost", port=6379, db=0)
|
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
|
||||||
|
|
||||||
|
states: States = States()
|
||||||
|
|
||||||
|
|
||||||
def next_action():
|
def next_action():
|
||||||
|
@ -26,9 +47,9 @@ def next_action():
|
||||||
Returns:
|
Returns:
|
||||||
int: key used by mgba
|
int: key used by mgba
|
||||||
"""
|
"""
|
||||||
votes = list(map(int, r.mget(KEYS)))
|
votes: list[int] = list(map(int, r.mget(KEYS_ID)))
|
||||||
if any(votes):
|
if any(votes):
|
||||||
r.mset(REDIS_INIT)
|
r.mset(KEYS_RESET)
|
||||||
return votes.index(max(votes))
|
return votes.index(max(votes))
|
||||||
else:
|
else:
|
||||||
return -1
|
return -1
|
||||||
|
@ -43,45 +64,77 @@ stream = Popen(
|
||||||
"-vcodec",
|
"-vcodec",
|
||||||
"png",
|
"png",
|
||||||
"-r",
|
"-r",
|
||||||
f"{FPS}",
|
f"{EMULATOR_FPS}",
|
||||||
"-s",
|
"-s",
|
||||||
f"{WIDTH}x{HEIGHT}",
|
f"{EMULATOR_WIDTH}x{EMULATOR_HEIGHT}",
|
||||||
"-i",
|
"-i",
|
||||||
"-",
|
"-",
|
||||||
"-f",
|
"-f",
|
||||||
"flv",
|
"flv",
|
||||||
"-s",
|
"-s",
|
||||||
f"{WIDTH}x{HEIGHT}",
|
f"{FFMPEG_WIDTH}x{FFMPEG_HEIGHT}",
|
||||||
"-r",
|
"-r",
|
||||||
"30",
|
f"{FFMPEG_FPS}",
|
||||||
"-b:v",
|
"-b:v",
|
||||||
"2M",
|
FFMPEG_BITRATE,
|
||||||
"-fflags",
|
"-fflags",
|
||||||
"nobuffer",
|
"nobuffer",
|
||||||
"-flags",
|
"-flags",
|
||||||
"low_delay",
|
"low_delay",
|
||||||
"-strict",
|
"-strict",
|
||||||
"experimental",
|
"experimental",
|
||||||
"rtmp://localhost:1935/live/test",
|
# "-loglevel",
|
||||||
|
# "quiet",
|
||||||
|
RTMP_STREAM_URI,
|
||||||
],
|
],
|
||||||
stdin=PIPE,
|
stdin=PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
last_frame_t = time.time()
|
def state_manager(loop):
|
||||||
|
print("ici")
|
||||||
|
ps = r.pubsub()
|
||||||
|
ps.subscribe("admin")
|
||||||
|
while True:
|
||||||
|
for message in ps.listen():
|
||||||
|
logging.debug(message)
|
||||||
|
if message["type"] == "message":
|
||||||
|
data = message["data"].decode("utf-8")
|
||||||
|
if data == "save":
|
||||||
|
asyncio.ensure_future(states.save(core), loop=loop)
|
||||||
|
elif data.startswith("load:"):
|
||||||
|
asyncio.ensure_future(states.load(core, data.removeprefix("load:")), loop=loop)
|
||||||
|
|
||||||
if not (core.frame_counter % POLLING_RATE):
|
|
||||||
core.clear_keys(*MGBA_KEYS)
|
|
||||||
next_key = next_action()
|
|
||||||
if next_key != -1:
|
|
||||||
core.set_keys(next_key)
|
|
||||||
|
|
||||||
core.run_frame()
|
async def emulator():
|
||||||
|
while True:
|
||||||
|
last_frame_t = time.time()
|
||||||
|
|
||||||
image = screen.to_pil().convert("RGB")
|
if not (core.frame_counter % EMULATOR_POLLING_RATE):
|
||||||
image.save(stream.stdin, "PNG")
|
core.clear_keys(*KEYS_MGBA)
|
||||||
|
next_key = next_action()
|
||||||
|
if next_key != -1:
|
||||||
|
core.set_keys(next_key)
|
||||||
|
|
||||||
sleep_t = last_frame_t - time.time() + SPF
|
core.run_frame()
|
||||||
if sleep_t > 0:
|
|
||||||
time.sleep(sleep_t)
|
image = screen.to_pil().convert("RGB")
|
||||||
|
image.save(stream.stdin, "PNG")
|
||||||
|
|
||||||
|
sleep_t = last_frame_t - time.time() + EMULATOR_SPF
|
||||||
|
if sleep_t > 0:
|
||||||
|
await asyncio.sleep(sleep_t)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(loop):
|
||||||
|
thread = threading.Thread(target=state_manager, args=(loop,))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
task_emulator = loop.create_task(emulator())
|
||||||
|
await task_emulator
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main(loop))
|
||||||
|
loop.close()
|
||||||
|
|
|
@ -6,13 +6,22 @@ import time
|
||||||
import redis
|
import redis
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
from settings import KEYS, PASSWORD_ADMIN, REDIS_INIT, USER_TIMEOUT
|
from settings import (
|
||||||
|
KEYS_ID,
|
||||||
|
KEYS_RESET,
|
||||||
|
PASSWORD_ADMIN,
|
||||||
|
REDIS_HOST,
|
||||||
|
REDIS_PORT,
|
||||||
|
USER_TIMEOUT,
|
||||||
|
WEBSOCKET_LISTEN,
|
||||||
|
WEBSOCKET_PORT,
|
||||||
|
)
|
||||||
from utils import User, Users
|
from utils import User, Users
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
r = redis.Redis(host="localhost", port=6379, db=0)
|
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
|
||||||
r.mset(REDIS_INIT)
|
r.mset(KEYS_RESET)
|
||||||
|
|
||||||
USERS: Users = Users()
|
USERS: Users = Users()
|
||||||
|
|
||||||
|
@ -31,16 +40,27 @@ async def parse_message(user: User, message: dict[str, str]) -> None:
|
||||||
logging.debug(f"admin authenticated: {user}")
|
logging.debug(f"admin authenticated: {user}")
|
||||||
await user.send('{"auth":"success"}')
|
await user.send('{"auth":"success"}')
|
||||||
|
|
||||||
|
if "admin" in message:
|
||||||
|
if user == USERS.admin:
|
||||||
|
data = message["admin"]
|
||||||
|
if data == "save":
|
||||||
|
r.publish("admin", "save")
|
||||||
|
elif data == data.startswith("load:"):
|
||||||
|
r.publish("admin", data)
|
||||||
|
else:
|
||||||
|
logging.error(f"unsupported admin action: {data}")
|
||||||
|
else:
|
||||||
|
logging.error(f"user is not admin: {user}")
|
||||||
|
|
||||||
if "action" in message:
|
if "action" in message:
|
||||||
data = message["action"]
|
data = message["action"]
|
||||||
|
|
||||||
if user.last_message + USER_TIMEOUT > time.time():
|
if user.last_message + USER_TIMEOUT > time.time():
|
||||||
logging.debug(f"dropping action: {data}")
|
logging.debug(f"dropping action: {data}")
|
||||||
return None
|
return None
|
||||||
elif data in KEYS:
|
elif data in KEYS_ID:
|
||||||
r.incr(data)
|
r.incr(data)
|
||||||
user.last_message = time.time()
|
user.last_message = time.time()
|
||||||
user.has_voted = True
|
|
||||||
else:
|
else:
|
||||||
logging.error(f"unsupported action: {data}")
|
logging.error(f"unsupported action: {data}")
|
||||||
|
|
||||||
|
@ -67,7 +87,7 @@ async def handler(websocket, path: str):
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
"""Start the websocket server."""
|
"""Start the websocket server."""
|
||||||
async with websockets.serve(handler, "localhost", 6789):
|
async with websockets.serve(handler, WEBSOCKET_LISTEN, WEBSOCKET_PORT): # nosec
|
||||||
await asyncio.Future() # run forever
|
await asyncio.Future() # run forever
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,36 @@
|
||||||
WIDTH: int = 240
|
from os import getenv
|
||||||
HEIGHT: int = 160
|
|
||||||
|
|
||||||
URI: str = "ws://127.0.0.1:6789/"
|
WEBSOCKET_HOST: str = getenv("WEBSOCKET_HOST", "localhost")
|
||||||
PASSWORD_ADMIN: str = "password_admin"
|
WEBSOCKET_PORT: int = int(getenv("WEBSOCKET_PORT", 6789))
|
||||||
|
WEBSOCKET_URI: str = f"ws://{WEBSOCKET_HOST}:{WEBSOCKET_PORT}/"
|
||||||
|
WEBSOCKET_LISTEN: str = getenv("WEBSOCKET_LISTEN", "localhost")
|
||||||
|
|
||||||
FPS: int = 60
|
RTMP_HOST: str = getenv("RTMP_HOST", "localhost")
|
||||||
SPF: float = 1.0 / FPS
|
RTMP_PORT: int = int(getenv("RTMP_PORT", 1935))
|
||||||
HZ: int = 10
|
RTMP_URI: str = f"rtmp://{RTMP_HOST}:{RTMP_PORT}/"
|
||||||
POLLING_RATE: int = FPS // HZ
|
RTMP_STREAM_PATH: str = getenv("RTMP_STREAM_PATH", "live")
|
||||||
USER_TIMEOUT: float = 0.5
|
RTMP_STREAM_KEY: str = getenv("RTMP_STREAM_KEY", "test")
|
||||||
|
RTMP_STREAM_URI: str = RTMP_URI + f"{RTMP_STREAM_PATH}/{RTMP_STREAM_KEY}"
|
||||||
|
|
||||||
|
REDIS_HOST: str = getenv("REDIS_HOST", "localhost")
|
||||||
|
REDIS_PORT: int = int(getenv("REDIS_PORT", 6379))
|
||||||
|
|
||||||
|
EMULATOR_WIDTH: int = int(getenv("EMULATOR_WIDTH", 240))
|
||||||
|
EMULATOR_HEIGHT: int = int(getenv("EMULATOR_HEIGHT", 160))
|
||||||
|
EMULATOR_FPS: int = int(getenv("EMULATOR_FPS", 60))
|
||||||
|
EMULATOR_SPF: float = 1.0 / EMULATOR_FPS
|
||||||
|
EMULATOR_INPUT_HZ: int = int(getenv("EMULATOR_INPUT_HZ", 10))
|
||||||
|
EMULATOR_POLLING_RATE: int = EMULATOR_FPS // EMULATOR_INPUT_HZ
|
||||||
|
EMULATOR_ROM_PATH: str = getenv("EMULATOR_ROM_PATH", "roms/pokemon.gba")
|
||||||
|
|
||||||
|
FFMPEG_WIDTH: int = int(getenv("FFMPEG_WIDTH", EMULATOR_WIDTH))
|
||||||
|
FFMPEG_HEIGHT: int = int(getenv("FFMPEG_HEIGHT", EMULATOR_HEIGHT))
|
||||||
|
FFMPEG_FPS: int = int(getenv("FFMPEG_FPS", 30))
|
||||||
|
FFMPEG_BITRATE: str = getenv("FFMPEG_BIRATE", "2M")
|
||||||
|
|
||||||
|
PASSWORD_ADMIN: str = getenv("PASSWORD_ADMIN", "password_admin")
|
||||||
|
|
||||||
|
USER_TIMEOUT: float = float(getenv("USER_TIMEOUT", 0.5))
|
||||||
|
|
||||||
KEYMAP: dict[str, int] = {
|
KEYMAP: dict[str, int] = {
|
||||||
"a": 0,
|
"a": 0,
|
||||||
|
@ -22,6 +44,6 @@ KEYMAP: dict[str, int] = {
|
||||||
"r": 8,
|
"r": 8,
|
||||||
"l": 9,
|
"l": 9,
|
||||||
}
|
}
|
||||||
KEYS: tuple = tuple(KEYMAP.keys())
|
KEYS_ID: tuple = tuple(KEYMAP.keys())
|
||||||
MGBA_KEYS: tuple = tuple(KEYMAP.values())
|
KEYS_MGBA: tuple = tuple(KEYMAP.values())
|
||||||
REDIS_INIT: dict = dict([(x, 0) for x in KEYS])
|
KEYS_RESET: dict = dict([(x, 0) for x in KEYS_ID])
|
||||||
|
|
33
src/utils.py
33
src/utils.py
|
@ -1,15 +1,17 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from mgba._pylib import ffi
|
||||||
|
|
||||||
|
|
||||||
class User:
|
class User:
|
||||||
"""Store infos related to a connected user."""
|
"""Store infos related to a connected user."""
|
||||||
|
|
||||||
websocket: Any
|
websocket: Any
|
||||||
last_message: float
|
last_message: float
|
||||||
has_voted: bool
|
|
||||||
|
|
||||||
def __init__(self, websocket: Any) -> None:
|
def __init__(self, websocket: Any) -> None:
|
||||||
"""Construct a User object.
|
"""Construct a User object.
|
||||||
|
@ -19,7 +21,6 @@ class User:
|
||||||
"""
|
"""
|
||||||
self.websocket = websocket
|
self.websocket = websocket
|
||||||
self.last_message = time.time()
|
self.last_message = time.time()
|
||||||
self.has_voted = False
|
|
||||||
|
|
||||||
async def send(self, data: str):
|
async def send(self, data: str):
|
||||||
"""Send data through the user's websocket.
|
"""Send data through the user's websocket.
|
||||||
|
@ -42,7 +43,6 @@ class User:
|
||||||
class Users(set):
|
class Users(set):
|
||||||
"""Store `User`s connected to the server."""
|
"""Store `User`s connected to the server."""
|
||||||
|
|
||||||
emulator: Optional[User] = None
|
|
||||||
admin: Optional[User] = None
|
admin: Optional[User] = None
|
||||||
|
|
||||||
def register(self, user: User):
|
def register(self, user: User):
|
||||||
|
@ -63,7 +63,26 @@ class Users(set):
|
||||||
self.remove(user)
|
self.remove(user)
|
||||||
logging.debug(f"user unregistered: {self}")
|
logging.debug(f"user unregistered: {self}")
|
||||||
|
|
||||||
def clear(self) -> None:
|
|
||||||
"""Clear the `has_voted` of each user in the set."""
|
class States(set):
|
||||||
for user in self:
|
"""Save and load states from files."""
|
||||||
user.has_voted = False
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Construct a `States` object."""
|
||||||
|
files = os.listdir("states")
|
||||||
|
states = list(filter(lambda x: x.endswith(".state"), files))
|
||||||
|
self.update(states)
|
||||||
|
|
||||||
|
async def save(self, core):
|
||||||
|
state = core.save_raw_state()
|
||||||
|
with open(f"states/{time.strftime('%Y-%m-%dT%H:%M:%S')}.state", "wb") as state_file:
|
||||||
|
for byte in state:
|
||||||
|
state_file.write(byte.to_bytes(4, byteorder="big", signed=False))
|
||||||
|
self.add(state)
|
||||||
|
|
||||||
|
async def load(self, core, filename):
|
||||||
|
state = ffi.new("unsigned char[397312]") # pulled 397312 from my ass
|
||||||
|
with open(f"states/{filename}.state", "rb") as state_file:
|
||||||
|
for i in range(len(state)):
|
||||||
|
state[i] = int.from_bytes(state_file.read(4), byteorder="big", signed=False)
|
||||||
|
core.load_raw_state(state)
|
||||||
|
|
Loading…
Reference in a new issue