zhad3 / zrenderer-fluxcp-addon Public
Tutorials
Setup zrenderer
This tutorial will guide you through setting up the zrenderer service together with this fluxcp addon. It touches quite some topics and might be overwhelming at first. But be patient, read everything carefully and hopefully enjoy the result ;)
After this tutorial you should have a system running that will render the characters on your server through a simple URL like any other picture. You can display them inside your FluxCP or also link them in Discord or any other site really.
Overview
This diagram gives an overview of the different services that interact with each other. It also shows which parts we will be editing through out the tutorial.

The green components will need to be setup/modified.
0. Dependencies
For this tutorial we will make use of Docker (or another container engine such as Podman) to extract data from the GRFs on your server and to run the zrenderer service.
For installation of Docker please take a look at https://docs.docker.com/engine/install/ and follow the installation steps for your server's distribution. Podman's installation guide can be found here: https://podman.io/getting-started/installation.
Again, you only need either one. No need to install both. The commands are mostly the same. E.g. docker run and podman run are the same. Through out the tutorial we will use docker for our commands.
If you decide not to use a container engine then you will have to compile the used tools on your server which is not covered in this tutorial. Please refer to the build instructions on the respective tools repositories.
Without further ado, let's start!
1. Uploading your data
For zrenderer to work it will need to have access to the client data just like the original client. These are the files stored in the data.grf, rdata.grf, my-custom-data.grf and so on. Basically whatever you have defined in your DATA.ini is required.
Important: The tool we will use to extract the data from your *.grf files can only extract the data from unencrypted GRFs! So make sure you upload only the GRF which have not been encrypted using e.g. GRF Editors encryption functionality.
First we will create some directories in your server. For that we recommend to directly ssh into your server but you can also use another tool of your liking.
We need to create the following directories:
/srv
└── zren
├── extracted-data
└── output
We used /srv/zren here where /srv is the default path for our webserver. This can be different for you! You can check for existing folders on your server: /srv/www, /var/www, /var/www-data. If for example you do not have /srv but instead have /var/www-data then use /var/zren instead. Make sure you replace any command that uses /srv/zren with /var/zren then instead in this tutorial!
SSH into your server: ssh <user>@my-server.net. Where <user> is a user on your server or root and my-server.net is either your server's hostname or IP address.
E.g.: ssh root@123.123.123.55.
Next create the directories:
mkdir /srv/zren
mkdir /srv/zren/data-resources
mkdir /srv/zren/output
The directory data-resources will hold the extracted files from your GRFs and output will be the output directory for the rendered sprites generated by zrenderer.
With that done, you will need to upload your GRFs. How you upload the data is up to you, you can use FTP, scp command line tool, a web frontend provided by your server provider or any other tool you usually use.
Check your DATA.ini. Example:
[Data]
0=rathena_resources.grf
1=renewal20190427.grf
2=data.grf
3=rdata.grfYours will different what is important is to know which GRFs you need to upload. In this case we need to upload the 4 unencrypted GRFs defined in the DATA.ini.
Upload the GRFs into the /srv/zren directory. It should then look like this for our example:
/srv
└── zren
├── data-resources
├── output
├── data.grf
├── rathena_resources.grf
├── rdata.grf
└── renewal20190427.grf
Great you uploaded the necessary GRFs files! Before we start extracting the files with another tool called zextractor we will create a filters.txt file. This limits the files we will extract to just the ones we require for zrenderer to function. This includes e.g. sprites, palettes and lua files.
Head over to https://github.com/zhad3/zrenderer/blob/main/RESOURCES.md and copy the filters into a file called filters.txt. Upload the filters.txt file you just created to the server in the same directory where the GRF are: /srv/zren/filters.txt.
Great! Now we can start extracting the GRFs. To do that you will need to SSH into your server (FTP cannot do that) and run the zextractor on the GRFs you uploaded:
cd /srv/zren
docker run --rm -v /srv/zren:/zext/input zhade/zextractor --outdir=input/data-resources --grf=input/rdata.grf,input/data.grf,input/renewal20190427.grf,input/rathena_resources.grf --filtersfile=input/filters.txt --verbose
Pay attention to the order of the *.grf files provided to the --grf option. It should be the reversed order of the DATA.ini. For example rdata.grf was defined last with 4=rdata.grf in DATA.ini. Hence here it is defined as the first grf.
In the command line you should see it downloading the zextractor container image and then start extracting the files. This will take some time. So lay back and take a break until it finishes.
...
[Info] [59439/59443] data\sprite\인간족\크루세이더\크루세이더_여_1529.act
[Info] [59440/59443] data\sprite\로브\GiantCatBag\남\페코rebellion_남.act
[Info] [59441/59443] data\sprite\로브\Wings_Of_Lucifer\여\그리폰가드_여.act
[Info] [59442/59443] data\palette\몸\costume_1\미케닉_마도기어_남_0_1.pal
[Info] [59443/59443] data\sprite\로브\기린의날개\여\WINDHAWK_RIDING_여.spr
[Info] Finished extracting after 17 minutes, 3 secs, 335 ms, 735 μs, and 2 hnsecs
If done you can check if the directory /srv/zren/data-resources contains the structure you would expect from a GRF.
With that done you can delete the GRFs files you uploaded unless you want to keep them in case some error occurrred.
rm *.grf
The data is now available on your server and we can continue setting up the zrenderer service!
2. Setup zrenderer
First let us create a config file that the zrenderer service will use. For that copy the default zrenderer.example.conf file available here: https://github.com/zhad3/zrenderer/blob/main/zrenderer.example.conf
We will now make some adjustments:
Set the output to be container/output (make sure to remove the preceding colon ';')
; Output directory where all rendered sprites will be saved to.
; Default value: output
outdir=container/outputSet the resourcepath to be container/data-resources
; Path to the resource directory. All resources are tried to be found within
; this directory.
; Default value:
resourcepath=container/data-resourcesSet the enableUniqueFilenames to true
; If enabled the output filenames will be the checksum of input parameters. This
; will ensure that each request creates a filename that is unique to the input
; parameters and no overlapping for the same job occurs.
; Default value: false
enableUniqueFilenames=trueSet the tokenfile to container/accesstokens.conf
; Access tokens file. File in which access tokens will be stored in. If the file
; does not exist it will be generated.
; Default value: accesstokens.conf
tokenfile=container/accesstokens.confAnd finally under the [server] group set hosts to 0.0.0.0
[server]
; Hostnames of the server. Can contain multiple comma separated values.
; Default value: localhost
hosts=0.0.0.0Save the file and upload it to your server under /srv/zren/zrenderer.conf.
Now let us start the server! Assuming you are still connected to your server with SSH and still in the /srv/zren directory:
docker run -d --name zren -v /srv/zren:/zren/container:Z -v /srv/zren/zrenderer.conf:/zren/zrenderer.conf -p 11011:11011 zhade/zrenderer
If no error occurred we should now see be able to see it running.
To check for errors run
docker logs zren
which should print:
Created access token file including a randomly generated admin token: o8etp42fx4zp0rvc3pj10qruxj04yqnk
[main(----) INF] Listening for requests on http://localhost:11011/
[main(----) INF] Listening for requests on http://0.0.0.0:11011/
In this case all is good. Upon first starting the server it has generated an access token for us which we will require later for the fluxcp addon. For that we need to store the token o8etp42fx4zp0rvc3pj10qruxj04yqnk. Just save it in a text file on your local machine for now.
Make sure you keep that token secret.
If you see an error like this:
std.exception.ErrnoException@std/stdio.d(530): Cannot open file `container/accesstokens.conf' in mode `w' (Permission denied)
means that the docker container has no access to our /srv/zren directory. Without going deep into permission system of Docker/Podman you can change the docker run ... command earlier to also include -u root:
docker rm zren
docker run -d --name zren -u root -v /srv/zren:/zren/container:Z -v /srv/zren/zrenderer.conf:/zren/zrenderer.conf -p 11011:11011 zhade/zrenderer
This should fix the permission problem (if it occurred).
The server should be running now try the command docker ps it should print something like this:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c54775b6a5b9 docker.io/zhade/zrenderer:latest ./zrenderer-serve... 7 seconds ago Up 5 seconds ago 0.0.0.0:11011->11011/tcp zren
An additional check you can do is try out our token that was generated by issuing the following curl command (make sure you use your earlier generated token):
curl localhost:11011/token/info?accesstoken=o8etp42fx4zp0rvc3pj10qruxj04yqnk
Which should return something like this:
{
"properties": {
"maxJobIdsPerRequest": -1,
"maxRequestsPerHour": -1
},
"capabilities": {
"modifyAccessTokens": false,
"readAccessTokens": false,
"readHealth": false,
"revokeAccessTokens": false,
"createAccessTokens": false
}
}Don't let the output fool you, this is the admin token which means it has all rights. What is important is that the server output the information.
Great! With that zrenderer should be set up we are ready to go to the next step!
3. Setup zrenderer-fluxcp-addon
Before we install the FluxCP addon we need to create some directories. Inside your FluxCP create: data/player/ and data/logs/errors/zren/. We also need a "placeholder" that wil be shown to the user if a character is not found or some other error occurred. For that a specific image will be used called _nothing.png and needs to be placed under
data/player/_nothing.png
You may use one of the two examples here:
.
Now you can install the zrenderer FluxCP Addon either by cloning the repository with git or download the zip and upload the code via your file transfer tool of choosing. To download the zip click on the green "Code" button and then "Download ZIP". To clone the repository SSH into your server and navigate to your FluxCP addon folder then run the command git clone https://github.com/zhad3/zrenderer-fluxcp-addon.git renderplayer
If you upload the code directly instead of using git make sure you upload them in the folder addons/renderplayer/.
Great with that done we need to modify the renderplayer/config/addon.php file which holds the configuration.
First thing we want to do is set the our zrenderer access token which we saved from earlier. Set the config AccessTokens.ADMIN to use our token:
'AccessTokens' => array(
'ADMIN' => 'o8etp42fx4zp0rvc3pj10qruxj04yqnk',
'MOD' => '',
'RENDERING' => ''
),The MOD token you see currently does not serve any purpose so we will ignore that. The RENDERING token will be used to actually create the characters images we will set that in a bit.
Save the addon.php file on your server and we will now login to FluxCP with an admin account. After logging in you should be able to see the new Zrenderer link appear (depending on your theme it might not be visible).

Click on that or go to http://your-server.net/<fluxcp>/renderplayer/health/. You should see a button called Check health. Feel free to click on that. It should report that the server is Up.
Anyway what we want to do is create a new access token. You should see a new link in the menu called New token. Click on that or go to http://your-servernet/<fluxcp>/renderplayer/newtoken/
A form should appear, for our token we set the maxJobIdsPerRequest and maxRequestsPerHour to -1 the rest we leave as the default/empty and put in a description called FluxCP which means FluxCP is using this token.

Hit Submit and a message should appear like this:
Response: Success! Id: 1, token: n24a9aj5aes52yuljq2lnq3yrrkqa8rq
In case you're wondering what these fields mean you can look them up here: https://z0q.neocities.org/ragnarok-online-tools/zrenderer/api/#TokenCapabilities.
Copy the token we just created: n24a9aj5aes52yuljq2lnq3yrrkqa8rq. We will put that in the addon.php which we edited earlier under the AccessTokens.RENDERING option:
'AccessTokens' => array(
'ADMIN' => 'o8etp42fx4zp0rvc3pj10qruxj04yqnk',
'MOD' => '',
'RENDERING' => 'n24a9aj5aes52yuljq2lnq3yrrkqa8rq'
),Save and upload it to your server. With that the main configuration is done!
You can test this by going to the following url: http://your-server.net/<fluxcp>/renderplayer/?name=<character name>. You will need to place a character name that exists on your server: e.g. http://your-server.net/<fluxcp>/renderplayer/?name=James.
Additional configuration
Cache
Per default a cache is enabled and set for 24 hours. Means if a characters image is rendered it won't change for 24 hours. You may want to decrease that time in the addon.php file. It is recommended to keep the cache on though to reduce the server load.
Rendering Groups
If you take a look at the addon.php file you will find a section called rendering with a default config that sets an option such as canvas, action, etc. That is used to request a specific action/animation of the characters image. A list of available actions can be viewed here https://github.com/zhad3/zrenderer-fluxcp-addon/blob/main/lib/actions.php. If you change this for example to ATTACK the character will perform the attacking animation. The canvas option defines the bounds of the created image and the position of the character. The default 150x150+75+125 means that the width is 150px and the height is also 150px and the character will be positioned at 75px from the left edge and 125px from the top edge of the image. If you remove this option all together then the created image will be fit just the character.
You may define multiple rendering groups. The default is when no other group is provided in the url or when the provided group does not exist. For example we may want to create another set of options for a pvp ranking display. Say we want the characters to be in their ready stance and face to their left:
'rendering' => array(
'default' => array(
'canvas' => '150x150+75+125',
'action' => 'STAND',
'direction' => 0,
'headdir' => 0
),
'pvp' => array(
'action' => 'ATTACKREADY',
'direction' => 7
)
),We have added a new group called pvp and set the appropriate action to ATTACKREADY and direction to 7. Try to render this group via: http://your-server.net/<fluxcp>/renderplayer/?name=James&group=pvp (using James as an example from before).
Great! With that the addon should be configured if you want to experiment a bit more it is recommended to disable the cache for the time being and re-enable it when you're done.
4. Edit nginx/httpd config
Before we had to always use the link such as http://your-server.net/<fluxcp>/renderplayer/?name=James to see the character. Here we will do some configuration to enable a nice link which look like this:
http://your-server.net/<fluxcp>/data/player/James.png
or
http://your-server.net/<fluxcp>/data/player/pvp/James.png
Apache/httpd
Before you continue make sure your httpd conf has enabled the mod_rewrite module. Check your
/usr/local/apache2/conf/httpd.conf
and find the line
LoadModule rewrite_module modules/mod_rewrite.so
If it contains a # in front, remove it and you're good to go.
You will now need to edit your virtualhost conf. If you have never touched a configuration of httpd before there should be a default one under
/usr/local/apache2/conf/extra/httpd-default.conf
or
/usr/local/apache2/conf/extra/httpd-vhosts.conf
or something like that. We're looking for a config that defines your webserver:
<VirtualHost *:80>
DocumentRoot "/srv/www"
...
</VirtualHost>
Inside the <VirtualHost> tag we want to create a new <Directory>:
<Directory "/srv/www/data/player">
Options -Indexes +FollowSymLinks
Require all granted
AllowOverride none
RewriteEngine On
RewriteBase "/"
RewriteCond %{REQUEST_URI} !(.+)\.png$ [NC,OR]
RewriteCond %{REQUEST_URI} _nothing\.png$ [NC]
RewriteRule ^ - [END]
RewriteRule ^(.+)/(.+)\.png$ index.php?module=renderplayer&name=$2&group=$1 [NC,QSA,END]
RewriteRule ^(.+)\.png$ index.php?module=renderplayer&name=$1 [NC,QSA,END]
</Directory>
Save that and reload httpd or restart your whole server. You should now be able to use the urls like this:
http://your-server.net/<fluxcp>/data/player/James.png
Nginx
If you're using Nginx you want to edit the configuration usually located under /etc/nginx/nginx.conf. Find the server entry you use for FluxCP. E.g.:
server {
listen 80;
root /srv/www;
...
}
and inside the server place the following lines:
location = /data/player/_nothing.png {
# serve normally
}
location ~ /data/player/(?<group>.+)/(?<name>.+)\.png$ {
rewrite ^ /index.php?module=renderplayer&name=$name&group=$group;
}
location ~ /data/player/(?<name>.+)\.png$ {
rewrite ^ /index.php?module=renderplayer&name=$name;
}
Save the config and reload nginx or restart your server. You should now be able to use the urls like this:
http://your-server.net/<fluxcp>/data/player/James.png
Done
You have completed the tutorial and the server should be fully functional and start serving your characters images. Have fun!