diff options
| -rw-r--r-- | docs/_coverpage.md | 17 | ||||
| -rw-r--r-- | docs/api/README.md | 92 | ||||
| -rw-r--r-- | docs/api/_sidebar.md | 7 | ||||
| -rw-r--r-- | docs/api/hooks.md | 110 | ||||
| -rw-r--r-- | docs/api/objects.md (renamed from docs/README.md) | 93 | ||||
| -rw-r--r-- | docs/api/packets.md | 1 | ||||
| -rw-r--r-- | docs/api/plugin.md | 76 | ||||
| -rw-r--r-- | docs/index.html | 7 | ||||
| -rw-r--r-- | docs/user/README.md | 66 | ||||
| -rw-r--r-- | docs/user/_sidebar.md | 3 | ||||
| -rw-r--r-- | docs/user/advanced.md | 90 | ||||
| -rw-r--r-- | docs/user/discord.md | 20 | ||||
| -rw-r--r-- | docs/user/img/step1.png | bin | 0 -> 46232 bytes | |||
| -rw-r--r-- | docs/user/img/step2.png | bin | 0 -> 38854 bytes | |||
| -rw-r--r-- | docs/user/img/step3.png | bin | 0 -> 25723 bytes | |||
| -rw-r--r-- | docs/user/img/step4.png | bin | 0 -> 40307 bytes | |||
| -rw-r--r-- | docs/user/img/step5.png | bin | 0 -> 225097 bytes | |||
| -rw-r--r-- | docs/user/img/step6.png | bin | 0 -> 47259 bytes |
18 files changed, 488 insertions, 94 deletions
diff --git a/docs/_coverpage.md b/docs/_coverpage.md new file mode 100644 index 0000000..78ec250 --- /dev/null +++ b/docs/_coverpage.md @@ -0,0 +1,17 @@ + + +# Sakuya AC + +> The Perfect and Elegant YSFlight Proxy Software + +- Extensible, has a well documented plugin API +- Fast, uses asyncio so that it doesn't lag +- Completely free and open source lisenced under GPLv3 + +Contributors +- Skipper +- Biry + +[Github](https://github.com/the-indian-dev/sakuya-ac) +[API Documentation](/api/README.md) +[User Documentation](/user/README.md) diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..736c0aa --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,92 @@ +# Sakuya AC API Documentation + +Sakuya AC : The perfect and elegant YSFlight Proxy Software. It is written in Python. Uses +asyncio so that it doesn't lag. This documentation will guide you through creating your +own Plugin for Sakuya AC. + +## Getting Started + +To get started with Sakuya AC, you need to have Python 3.9 or higher installed on your system. + +### Basic Structure of a Plugin + +There are two ways to work with the proxy: +1. Hooks : These actually modify the incoming/outgoing packets from YSF Server, these are blocking +and can be used to modify the packets. +- Example : G Limiter, Chat Filter; As these need to modify the packets at that instant + +2. Commands : These are non essential and do not modify the incoming/outgong packets, you can send your +own packets to server/client. These are non blocking. +- Example : Fog color changer, Ban command; these do not need to modify any commands at that instant + +- All plugins are saved in plugins directory in the root of the project. + +### Simple Command Plugin + +```python +""" +This is an example test command! +""" + +from lib import YSchat +from time import sleep + +ENABLED = False + +class Plugin: + def __init__(self): + self.plugin_manager = None + + def register(self, plugin_manager): + self.plugin_manager = plugin_manager + self.plugin_manager.register_command('test', self.test) + self.plugin_manager.register_command('timer', self.timer) + + def test(self, full_message, player, message_to_client, message_to_server): + message_to_client.append(YSchat.message("Test command received")) + return True + + def timer(self, full_message, player, message_to_client, message_to_server): + sleep(5) + message_to_client.append(YSchat.message("Timer ended")) + return True +``` +- You start the plugin by giving it a description, +- You must have a global ENABLED variable, which is set by the user + if they wish to enable the plugin or not +- You must have a class Plugin, which has a register method + use ``register_command`` to register a command. + -- ``self.plugin_manager.register_command('command_name', self.function_name)`` +- now self.function_name must take the shown parameters + +### Simple Hooks Plugin + +```python +"""This plugin will flash the lights/fog colour whenever a flight status update +is sent +It can be enabled here by changing the value of ENABLED to True.""" +from lib.PacketManager.packets import FSNETCMD_SKYCOLOR, FSNETCMD_FOGCOLOR +from random import randint +ENABLED = True + +class Plugin: + def __init__(self): + self.plugin_manager = None + + def register(self, plugin_manager): + self.plugin_manager = plugin_manager + self.plugin_manager.register_hook('on_flight_data', self.on_receive) + + def on_receive(self, data, player, messages_to_client, *args): + sky_colour_packet = FSNETCMD_SKYCOLOR.encode(randint(0, 255), randint(0, 255), randint(0, 255), True) + fog_colour_packet = FSNETCMD_FOGCOLOR.encode(randint(0, 255), randint(0, 255), randint(0, 255), True) + messages_to_client.append(sky_colour_packet) + messages_to_client.append(fog_colour_packet) + return True +``` +- Unlike the previous example, this uses a hook which modifies the packet from the server at that instant +- You must return True at the end of the function to indicate that the original packet will be sent + -- (In this case, the orginal packet is the flight data, not sending will cause the client to + not receive the flight data) +- returning False, means the orginal packet which triggered the hook will not be sent to the YSF server, +this is useful for chat filters etc. diff --git a/docs/api/_sidebar.md b/docs/api/_sidebar.md new file mode 100644 index 0000000..a8f77cf --- /dev/null +++ b/docs/api/_sidebar.md @@ -0,0 +1,7 @@ +* [Home](/api/README.md) +* [Plugin Structure](/api/plugin.md) +* [Class Reference](/api/objects.md) + - [Aircraft](/api/objects.md#aircraft-class) + - [Player](/api/objects.md#player-class) +* [Packet Reference](/api/packets.md) +* [Hooks Reference](/api/hooks.md) diff --git a/docs/api/hooks.md b/docs/api/hooks.md new file mode 100644 index 0000000..373b42f --- /dev/null +++ b/docs/api/hooks.md @@ -0,0 +1,110 @@ +# Introduction + +Hooks are blocking functions that are called when a specific event occurs. +They are used to modify the behavior of the proxy. Every hook function +must return a `bool` value, `True` if the packet that triggered the hook +should be sent to the client/server or `False` if the packet should be +dropped and not sent. + +# Hook Structure + +In main `Plugin` Class, there must be a `register` method, under which +you must declare your hooks. +> eg. +> ```python +> def register(self, plugin_manager): +> self.plugin_manager = plugin_manager +> self.plugin_manager.register_hook('on_flight_data', self.on_receive) +>``` +> This will trigger the `on_receive` method of your plugin when the `on_flight_data` hook is triggered. + +# Hook Function Structure + +Every hook function must have the following structure: +```python +def on_receive(self, data, player, message_to_client, message_to_server) +``` +Where: +- `data` is the packet object that triggered the hook, It is a `bytes` object. +The data is sent without the header.(that is the size of the packet) + +- `player` is the `Player` object that triggered the hook. + +- `message_to_client` is the list that contains the packets that will be sent to the client. + you must append to it and return a value for the packet to be sent. + +- `message_to_server` is the list that contains the packets that will be sent to the server. + you must append to it and return a value for the packet to be sent. + +> You must return a `bool` value, `True` if the packet that triggered the hook should be +> sent to the client/server or `False` if the packet should be dropped and not sent. + +# Hook List + +## Client to Server Side + +| Hook | Packet Object | +|-------------------------|-----------------------| +| `on_login` | `FSNETCMD_LOGON` | +| `on_logout` | `FSNETCMD_LOGOFF` | +| `on_error` | `FSNETCMD_ERROR` | +| `on_load_field` | `FSNETCMD_LOADFIELD` | +| `on_add_object` | `FSNETCMD_ADDOBJECT` | +| `on_readback` | `FSNETCMD_READBACK` | +| `on_smoke_color` | `FSNETCMD_SMOKECOLOR` | +| `on_join_request` | `FSNETCMD_JOINREQUEST`| +| `on_join_approval` | `FSNETCMD_JOINAPPROVAL`| +| `on_reject_join_request`| `FSNETCMD_REJECTJOINREQ`| +| `on_flight_data` | `FSNETCMD_AIRPLANESTATE`| +| `on_unjoin` | `FSNETCMD_UNJOIN` | +| `on_remove_airplane` | `FSNETCMD_REMOVEAIRPLANE`| +| `on_request_test_airplane`| `FSNETCMD_REQUESTTESTAIRPLANE`| +| `on_kill_server` | `FSNETCMD_KILLSERVER` | +| `on_prepare_simulation` | `FSNETCMD_PREPARESIMULATION`| +| `on_test_packet` | `FSNETCMD_TESTPACKET` | +| `on_lockon` | `FSNETCMD_LOCKON` | +| `on_remove_ground` | `FSNETCMD_REMOVEGROUND`| +| `on_missile_launch` | `FSNETCMD_MISSILELAUNCH`| +| `on_get_damage` | `FSNETCMD_GETDAMAGE` | +| `on_weapon_config` | `FSNETCMD_WEAPONCONFIG`| +| `on_air_cmd` | `FSNETCMD_AIRCMD` | +| `on_chat` | `FSNETCMD_TEXTMESSAGE`| +| `on_environment` | `FSNETCMD_ENVIRONMENT`| +| `on_sky_color` | `FSNETCMD_SKYCOLOR` | +| `on_fog_color` | `FSNETCMD_FOGCOLOR` | +| `on_list` | `FSNETCMD_LIST` | + +<br> + +## Server to Client Side + +| Hook | Packet Object | +|-------------------------------|-----------------------| +| `on_login_server` | `FSNETCMD_LOGON` | +| `on_logout_server` | `FSNETCMD_LOGOFF` | +| `on_error_server` | `FSNETCMD_ERROR` | +| `on_load_field_server` | `FSNETCMD_LOADFIELD` | +| `on_add_object_server` | `FSNETCMD_ADDOBJECT` | +| `on_readback_server` | `FSNETCMD_READBACK` | +| `on_smoke_color_server` | `FSNETCMD_SMOKECOLOR` | +| `on_join_request_server` | `FSNETCMD_JOINREQUEST`| +| `on_join_approval_server` | `FSNETCMD_JOINAPPROVAL`| +| `on_reject_join_request_server`| `FSNETCMD_REJECTJOINREQ`| +| `on_flight_data_server` | `FSNETCMD_AIRPLANESTATE`| +| `on_unjoin_server` | `FSNETCMD_UNJOIN` | +| `on_remove_airplane_server` | `FSNETCMD_REMOVEAIRPLANE`| +| `on_request_test_airplane_server`| `FSNETCMD_REQUESTTESTAIRPLANE`| +| `on_kill_server_server` | `FSNETCMD_KILLSERVER` | +| `on_prepare_simulation_server`| `FSNETCMD_PREPARESIMULATION`| +| `on_test_packet_server` | `FSNETCMD_TESTPACKET` | +| `on_lockon_server` | `FSNETCMD_LOCKON` | +| `on_remove_ground_server` | `FSNETCMD_REMOVEGROUND`| +| `on_missile_launch_server` | `FSNETCMD_MISSILELAUNCH`| +| `on_get_damage_server` | `FSNETCMD_GETDAMAGE` | +| `on_weapon_config_server` | `FSNETCMD_WEAPONCONFIG`| +| `on_air_cmd_server` | `FSNETCMD_AIRCMD` | +| `on_chat_server` | `FSNETCMD_TEXTMESSAGE`| +| `on_environment_server` | `FSNETCMD_ENVIRONMENT`| +| `on_sky_color_server` | `FSNETCMD_SKYCOLOR` | +| `on_fog_color_server` | `FSNETCMD_FOGCOLOR` | +| `on_list_server` | `FSNETCMD_LIST` | diff --git a/docs/README.md b/docs/api/objects.md index 9af29ef..bdd5d49 100644 --- a/docs/README.md +++ b/docs/api/objects.md @@ -1,96 +1,3 @@ -# Sakuya AC API Documentation - -Sakuya AC : The perfect and elegant YSFlight Proxy Software. It is written in Python. Uses -asyncio so that it doesn't lag. This documentation will guide you through creating your -own Plugin for Sakuya AC. - -## Getting Started - -To get started with Sakuya AC, you need to have Python 3.9 or higher installed on your system. - -### Basic Structure of a Plugin - -There are two ways to work with the proxy: -1. Hooks : These actually modify the incoming/outgoing packets from YSF Server, these are blocking -and can be used to modify the packets. -- Example : G Limiter, Chat Filter; As these need to modify the packets at that instant - -2. Commands : These are non essential and do not modify the incoming/outgong packets, you can send your -own packets to server/client. These are non blocking. -- Example : Fog color changer, Ban command; these do not need to modify any commands at that instant - -- All plugins are saved in plugins directory in the root of the project. - -### Simple Command Plugin - -```python -""" -This is an example test command! -""" - -from lib import YSchat -from time import sleep - -ENABLED = False - -class Plugin: - def __init__(self): - self.plugin_manager = None - - def register(self, plugin_manager): - self.plugin_manager = plugin_manager - self.plugin_manager.register_command('test', self.test) - self.plugin_manager.register_command('timer', self.timer) - - def test(self, full_message, player, message_to_client, message_to_server): - message_to_client.append(YSchat.message("Test command received")) - return True - - def timer(self, full_message, player, message_to_client, message_to_server): - sleep(5) - message_to_client.append(YSchat.message("Timer ended")) - return True -``` -- You start the plugin by giving it a description, -- You must have a global ENABLED variable, which is set by the user - if they wish to enable the plugin or not -- You must have a class Plugin, which has a register method - use ``register_command`` to register a command. - -- ``self.plugin_manager.register_command('command_name', self.function_name)`` -- now self.function_name must take the shown parameters - -### Simple Hooks Plugin - -```python -"""This plugin will flash the lights/fog colour whenever a flight status update -is sent -It can be enabled here by changing the value of ENABLED to True.""" -from lib.PacketManager.packets import FSNETCMD_SKYCOLOR, FSNETCMD_FOGCOLOR -from random import randint -ENABLED = True - -class Plugin: - def __init__(self): - self.plugin_manager = None - - def register(self, plugin_manager): - self.plugin_manager = plugin_manager - self.plugin_manager.register_hook('on_flight_data', self.on_receive) - - def on_receive(self, data, player, messages_to_client, *args): - sky_colour_packet = FSNETCMD_SKYCOLOR.encode(randint(0, 255), randint(0, 255), randint(0, 255), True) - fog_colour_packet = FSNETCMD_FOGCOLOR.encode(randint(0, 255), randint(0, 255), randint(0, 255), True) - messages_to_client.append(sky_colour_packet) - messages_to_client.append(fog_colour_packet) - return True -``` -- Unlike the previous example, this uses a hook which modifies the packet from the server at that instant -- You must return True at the end of the function to indicate that the original packet will be sent - -- (In this case, the orginal packet is the flight data, not sending will cause the client to - not receive the flight data) -- returning False, means the orginal packet which triggered the hook will not be sent to the YSF server, -this is useful for chat filters etc. - ## Object Descriptions ### ``Aircraft`` Class diff --git a/docs/api/packets.md b/docs/api/packets.md new file mode 100644 index 0000000..f10c9a1 --- /dev/null +++ b/docs/api/packets.md @@ -0,0 +1 @@ +Incomplete! diff --git a/docs/api/plugin.md b/docs/api/plugin.md new file mode 100644 index 0000000..2d05ece --- /dev/null +++ b/docs/api/plugin.md @@ -0,0 +1,76 @@ +# Single File Plugins + +These plugins are contained in a single `.py` files, recomemded for small plugins. + +```python +""" +This is an example test command! +""" + +from lib import YSchat + +# ENABLED variable must be present in your plugin otherwise it will +# fail to load. This is used by user to change the state of the plugin + +ENABLED = True + +class Plugin: + def __init__(self): + # Intialise the plugin here + self.plugin_manager = None + + def register(self, plugin_manager): + # Here you declare functions of your plugin + # Bind to plugin manager + self.plugin_manager = plugin_manager + # Register your plugin commands + self.plugin_manager.register_command('test', self.test) + # Register your plugin hooks + self.plugin_manager.register_hook('on_flight_data', self.on_receive) + + # Command Function + def test(self, full_message, player, message_to_client, message_to_server): + message_to_client.append(YSchat.message("Test command received")) + return True + + # Hook Function + def on_receive(self, data, player, message_to_client, message_to_server): + print(f"Received flight data of {player.username}") + return True +``` + +# Multi File Plugins + +These plugins are contained in a directory with multiple files, recomemded for large plugins. + +```bash +plugin/ +├── Plugin.py +└── __init__.py +``` + +```python +# __init__.py +from .Plugin import Plugin +from .Plugin import ENABLED +``` + +```python +# Plugin.py +""" +This is a multi file plugin example +""" +ENABLED = True + +class Plugin: + def __init__(self): + self.plugin_manager = None + + def register(self, plugin_manager): + self.plugin_manager = plugin_manager + self.plugin_manager.register_hook('on_flight_data', self.on_flight_data) + + def on_flight_data(self, data, player,message_to_client, message_to_server): + print(f"Received flight data of {player.username}") + return True +``` diff --git a/docs/index.html b/docs/index.html index 551272b..f653dd2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -15,9 +15,12 @@ <div id="app"></div> <script> window.$docsify = { - name: "Sakuya AC API Documentation", + name: "Sakuya AC", repo: "https://github.com/the-indian-dev/sakuya-ac", maxLevel: 3, + loadSidebar: true, + onlyCover: true, + coverpage: true, plugins: [ function (hook) { hook.doneEach(function () { @@ -33,5 +36,7 @@ <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script> <!-- Prism --> <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js"></script> + <!-- Collapsable side bar --> + <script src="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script> </body> </html> diff --git a/docs/user/README.md b/docs/user/README.md new file mode 100644 index 0000000..8626ae1 --- /dev/null +++ b/docs/user/README.md @@ -0,0 +1,66 @@ +## Introduction + +Sakuya AC has the following features: +1. Discord Chat sync +2. G Limiter +3. Plugin Support +etc. + +This guide will help you to install Sakuya AC on your server + +## Quick Start Guide + +1. Clone the git repository +```bash +git clone [email protected]:the-indian-dev/sakuya-ac +``` + +2. You will need Python 3.9 or above to run Sakuya AC +Please make sure you have correct version of Python + +3. Install the dependencies + +- For Windows +```bash +py -m pip install -r requirements.txt +``` +- For Debian, Ubuntu etc. +```bash +sudo apt install python3-aiohttp +``` +- For Arch Linux +```bash +sudo pacman -S python-aiohttp +``` + +4. Setup Configuration +- Open `config.py` in a text editor +- Start YSF Server and put its IP in `SERVER_HOST` and Port in `SERVER_PORT`. + The Proxy will run at the port given at `PROXY_PORT` + +5. Run the Proxy +```bash +python proxy.py +``` +You can now connect to the Proxy server at the port you specified in `PROXY_PORT` + +- Advanced Configuration is documented [here](/user/advanced.md) + +## Installing Plugins + +!> Plugins can be harmful to your computer, please install from trusted sources. + We are NOT responsible for installing plugins from untrusted sources. + +1. Download plugin from a trusted source +2. Put the plugin in the `plugins` directory + - If the plugin is a `.py` file, put it in the `plugins` directory + - If the plugin is a `.zip` file, extract it and put it in the `plugins` directory +3. Set the `ENABLED` variable in the plugin file to `True` +4. Run the proxy + +## Reporting Issues + +1. Please set logging level to `DEBUG` in `config.py` +2. Please provide the replay .yfs file for the issue +3. Provide steps to reproduce the bug +4. Start a new issue on our Github repository. diff --git a/docs/user/_sidebar.md b/docs/user/_sidebar.md new file mode 100644 index 0000000..fe12b7f --- /dev/null +++ b/docs/user/_sidebar.md @@ -0,0 +1,3 @@ +* [Getting Started](/user/README.md) +* [Advanced Configuration](/user/advanced.md) +* [Setting up Discord Chat Sync](/user/discord.md) diff --git a/docs/user/advanced.md b/docs/user/advanced.md new file mode 100644 index 0000000..aed5019 --- /dev/null +++ b/docs/user/advanced.md @@ -0,0 +1,90 @@ +# `config.py` Configuration + +The `config.py` file is the main configuration file for Sakuya AC. It contains the following configuration options: + +### `LOGGING_LEVEL` + +The logging level for the proxy. The logging level can be one of the following: +1. `DEBUG` : Logs all messages (Recomedded for debugging and reporting issues) +2. `INFO` : Logs only informational messages (Default, Recomended for normal use) +3. `WARNING` : Logs only warning messages +4. `CRTITICAL` : Logs only critical messages + +### `SERVER_HOST` + +The IP address of the YSF Server. The proxy will connect to this IP address. +For local server, use `"127.0.0.1"`, It takes string values, so make sure to +put quotaion marks around the IP address. + +### `SERVER_PORT` + +The port of the YSF Server. The proxy will connect to this port. The default is +`7915`, which is the default port for YSF Server. It takes integer values. Do not +put quotation marks around the port number. + +### `PROXY_PORT` + +The port on which the proxy will run. The default is `9000`. It takes integer values. + +### `WELCOME_MESSAGE` + +The welcome message that will be displayed when a client logs in to the YSFlight server. +It takes string values. You can use the following placeholders in the message: +- `{username}` : The username of the client +> eg. `Welcome {username} to the server!` +> This will display `Welcome Sakuya to the server!` when a client with username +> `Sakuya` logs in. + +### `PREFIX` + +This is the prefix for the chat commands in your server. The default is `/`. +It takes string values. Make sure to put quotation marks around the prefix. +> eg. If you set the prefix to `!`, then the command to change fog color would be `!fog` + +!> Messages starting with prefix will be treated as commands. Make sure to set a + prefix that is not used in normal chat messages. + +### `YSF_VERSION` + +The version for your YSFlight server. The default is `20150425`. It takes integer values. + +### `G_LIM` + +The G-Limiter value for your server. The default is `4`. It takes integer values. +The client will take damage if absolute value of their G force exceeds this value. + +- To change the interval at which the client takes G Force Damage, change the value of +`INTERVAL` in `plugins/over_g_damage.py`. The default is `0.2`, which means the client +takes damage every 0.2 seconds. + +### `HEALTH_HACK_MESSAGE` + +The message that will be displayed when a client tries to hack their health. It takes +string values. The username of the client is automatically appended to the end of the +message + +>eg. `HEALTH_HACK_MESSAGE = "Detected for Health Hack"` +> This will display `Detected for Health Hack by Sakuya` when a client tries to cheat. + +### `SMOKE_PLANE` + +If the plane should smoke when it has a life lower than `SMOKE_LIFE`. The default is `True`. + +### `SMOKE_LIFE` + +The life value below which the plane should start smoking. The default is `5`. It takes integer values. + +### `DISCORD_ENABLED` + +If the Discord Chat Sync should be enabled. The default is `False`. It takes boolean values. +You must `aiohttp` installed to use this feature. + +Setting up Discord Chat Sync is documented [here](/user/discord.md) + +### `DISCORD_TOKEN` + +The Discord Bot Token for the Discord Chat Sync. It takes string values. + +### `CHANNEL_ID` + +The Channel ID of the channel where the chat messages will be sent. It takes integer values. diff --git a/docs/user/discord.md b/docs/user/discord.md new file mode 100644 index 0000000..b6ea279 --- /dev/null +++ b/docs/user/discord.md @@ -0,0 +1,20 @@ +# Creating a Discord Bot account + +1. Go to [Discord Developer Portal](https://discord.com/developers/applications), + and login with your Discord account. +2. Click on `New Application` and give your bot a name. +  +3. Go to the left sidebar and click on `Bot`. +  +4. Click on `Reset Token` and copy the token. This your bot token for `DISCORD_TOKEN` in `config.py`. +  +5. Scroll down to `Privileged Gateway Intents`. +6. Enable **Message Content** Intent. This is important! +  + +# Getting Channel ID + +1. Go to your Discord server and right click on the channel where you want to sync the chat. +2. Click on `Copy ID` to copy the channel ID. +  +3. Paste the channel ID in `CHANNEL_ID` in `config.py`. diff --git a/docs/user/img/step1.png b/docs/user/img/step1.png Binary files differnew file mode 100644 index 0000000..e7b2447 --- /dev/null +++ b/docs/user/img/step1.png diff --git a/docs/user/img/step2.png b/docs/user/img/step2.png Binary files differnew file mode 100644 index 0000000..3eee04e --- /dev/null +++ b/docs/user/img/step2.png diff --git a/docs/user/img/step3.png b/docs/user/img/step3.png Binary files differnew file mode 100644 index 0000000..165f711 --- /dev/null +++ b/docs/user/img/step3.png diff --git a/docs/user/img/step4.png b/docs/user/img/step4.png Binary files differnew file mode 100644 index 0000000..0fd4edd --- /dev/null +++ b/docs/user/img/step4.png diff --git a/docs/user/img/step5.png b/docs/user/img/step5.png Binary files differnew file mode 100644 index 0000000..69fdf8a --- /dev/null +++ b/docs/user/img/step5.png diff --git a/docs/user/img/step6.png b/docs/user/img/step6.png Binary files differnew file mode 100644 index 0000000..f7f847f --- /dev/null +++ b/docs/user/img/step6.png |
