Difference between revisions of "Technical"

From Elma Wiki
Jump to navigation Jump to search
 
(59 intermediate revisions by 3 users not shown)
Line 3: Line 3:
 
== Elasto Mania ==
 
== Elasto Mania ==
 
'''Note:''' All Elasto Mania files are in little-endian format.
 
'''Note:''' All Elasto Mania files are in little-endian format.
 +
 
The type name will depend on what programming language you are working with, here's a short equivalency list between the most common ones as a reference:
 
The type name will depend on what programming language you are working with, here's a short equivalency list between the most common ones as a reference:
 
{| class="wikitable"
 
{| class="wikitable"
! Bytes || C || Python* || Rust
+
! Bytes || C || Python¹ || Rust
 +
|-
 +
| 1 || char || c || u8/i8²
 
|-
 
|-
| 1 || char || c || u8
+
| 1 || unsigned char || B || u8
 
|-
 
|-
 
| 2 || short || h || i16
 
| 2 || short || h || i16
Line 13: Line 16:
 
| 4 || int || i || i32
 
| 4 || int || i || i32
 
|-
 
|-
| 4 || uint || I || u32
+
| 4 || unsigned int || I || u32
 
|-
 
|-
 
| 4 || float || f || f32
 
| 4 || float || f || f32
Line 20: Line 23:
 
|}
 
|}
  
<nowiki>*</nowiki> Using struct formatting [https://docs.python.org/3/library/struct.html]
+
&sup1; Using struct formatting [https://docs.python.org/3/library/struct.html]<br />
 +
&sup2; Depends on context. Assume that all chars in Elma files are ASCII and never expected to be signed, use u8. Probably?
  
 
=== Levels ===
 
=== Levels ===
  <nowiki>8)</nowiki>
+
How to parse a level byte by byte. Remember little-endianess.
 +
{| class="wikitable"
 +
! Offset || Type || Bytes || Description
 +
|-
 +
| style="text-align:right;" | 0 || char[5] || style="text-align:right;" | 5 || "POT14" for Elma levels. Across files start with "POT06" (See Across section).
 +
|-
 +
| style="text-align:right;" | 5 || short || style="text-align:right;" | 2 || Lower short of int value below. No particular use for anything?
 +
|-
 +
| style="text-align:right;" | 7 || int || style="text-align:right;" | 4 || Random number to link replays to this version of the level. Editing a level will change this.
 +
|-
 +
| style="text-align:right;" | 11 || double || style="text-align:right;" | 8 || Integrity check 1 (See section below table for calculations).
 +
|-
 +
| style="text-align:right;" | 19 || double || style="text-align:right;" | 8 || Integrity check 2 (See section below table for calculations).
 +
|-
 +
| style="text-align:right;" | 27 || double || style="text-align:right;" | 8 || Integrity check 3 (See section below table for calculations).
 +
|-
 +
| style="text-align:right;" | 35 || double || style="text-align:right;" | 8 || Integrity check 4, different for locked levels (See section below table for calculations).
 +
|-
 +
| style="text-align:right;" | 43 || char[51] || style="text-align:right;" | 51 || Level name.
 +
|-
 +
| style="text-align:right;" | 94 || char[16] || style="text-align:right;" | 16 || LGR name.
 +
|-
 +
| style="text-align:right;" | 110 || char[10] || style="text-align:right;" | 10 || Ground texture name.
 +
|-
 +
| style="text-align:right;" | 120 || char[10] || style="text-align:right;" | 10 || Sky texture name.
 +
|-
 +
| style="text-align:right;" | 130 || double || style="text-align:right;" | 8 || Number of polygons + 0.4643643. Maximum 300 for normal Elma, 1000 for EOL.
 +
|-
 +
| style="text-align:right;" | 138 || polygon || style="text-align:right;" | polys * 24 || See polygon section below table for structure.
 +
|-
 +
| style="text-align:right;" | ? || double || style="text-align:right;" | 8 || Number of objects + 0.4643643. Maximum 52 for normal Elma, 252 for EOL.
 +
|-
 +
| style="text-align:right;" | ? || object || style="text-align:right;" | objs * 28 || See object section below table for structure.
 +
|-
 +
| style="text-align:right;" | ? || double || style="text-align:right;" | 8 || Number of pictures + 0.2345672. Maximum 5000.
 +
|-
 +
| style="text-align:right;" | ? || picture || style="text-align:right;" | pics * 54 || See picture section below table for structure.
 +
|-
 +
| style="text-align:right;" | ? || int || style="text-align:right;" | 4 || 0x3A106700 (0x0067103A) Optional: Marker for beginning of encrypted data
 +
|-
 +
| style="text-align:right;" | ? || top10 || style="text-align:right;" | 344 || Encrypted single-player top10 list. See top10 section below table for structure.
 +
|-
 +
| style="text-align:right;" | ? || top10 || style="text-align:right;" | 344 || Encrypted multi-player top10 list. See top10 section below table for structure.
 +
|-
 +
| style="text-align:right;" | ? || int || style="text-align:right;" | 4 || 0x525D8400 (0x00845D52) Marker for end of encrypted data
 +
|}
 +
 
 +
 
 +
{| class="wikitable"
 +
|+ Polygon structure
 +
|-
 +
! Type || Bytes || Description
 +
|-
 +
| int ||  style="text-align:right;" | 4 || Grass flag. 0 for normal polygon, 1 for grass.
 +
|-
 +
| int ||  style="text-align:right;" | 4 || Number of vertices.
 +
|-
 +
| vertex (double * 2) ||  style="text-align:right;" | vertices * 16 || X and Y as two doubles.
 +
|}
 +
 
 +
 
 +
{| class="wikitable"
 +
|+ Object structure
 +
|-
 +
! Type || Bytes || Description
 +
|-
 +
| double ||  style="text-align:right;" | 8 || X position.
 +
|-
 +
| double ||  style="text-align:right;" | 8 || Y position.
 +
|-
 +
| int ||  style="text-align:right;" | 4 || Object type.<br />1: Flower<br />2: Apple<br />3: Killer<br />4: Start
 +
|-
 +
| int ||  style="text-align:right;" | 4 || Gravity (if object is apple).<br />0: Normal<br />1: Up<br />2: Down<br />3: Left<br />4: Right
 +
|-
 +
| int ||  style="text-align:right;" | 4 || Animation (if object is apple) '''- 1'''. E.g. animation numbers 1 to 9 in editor will be 0-8.
 +
|}
 +
 
 +
 
 +
{| class="wikitable"
 +
|+ Picture structure
 +
|-
 +
! Type || Bytes || Description
 +
|-
 +
| char*10 || style="text-align:right;" | 10 || Normal picture name.
 +
|-
 +
| char*10 || style="text-align:right;" | 10 || Texture name.
 +
|-
 +
| char*10 || style="text-align:right;" | 10 || Mask name.
 +
|-
 +
| double || style="text-align:right;" | 8 || X position.
 +
|-
 +
| double || style="text-align:right;" | 8 || Y position.
 +
|-
 +
| int || style="text-align:right;" | 4 || Z-distance, 1-999.
 +
|-
 +
| int || style="text-align:right;" | 4 || Clipping, 0 = Unclipped, 1 = Ground, 2 = Sky.
 +
|}
 +
 
 +
 
 +
{| class="wikitable"
 +
|+ Top10 structure
 +
|-
 +
! colspan="3" style="color: #8d0000; background-color: #aaa" | Data needs to be decrypted first. See algorithm below table.
 +
|-
 +
! Type || Bytes || Description
 +
|-
 +
| int || 4 || Number of times in top10 list.
 +
|-
 +
| int*10 || 40 || Array with times, in hundredths.
 +
|-
 +
| char*10*15 || 150 || Array with player 1 names, each 15 chars.
 +
|-
 +
| char*10*15 || 150 || Array with player 2 names, each 15 chars.
 +
|}
 +
 
 +
This structure is for '''one''' top 10 list.<br />
 +
You need to read '''both''' top10 lists ('''688 bytes''') and decrypt them together with the following algorithm:
 +
 
 +
  <nowiki>char *buf = TOP10S; // the 688 bytes of data
 +
short ebp8 = 0x15;
 +
short ebp10 = 0x2637;
 +
for (int x = 0; x < 688; x++) {
 +
  buf[x] ^= ebp8 & 0xFF; // note the bitwise operations
 +
  ebp10 += (ebp8 % 0xD3D) * 0xD3D;
 +
  ebp8 = ebp10 * 0x1F + 0xD3D;
 +
}</nowiki>
 +
You can use the exact algorithm for encrypting the top10 lists back if you're creating a level or editing lists as well.
 +
 
 +
 
 +
'''Integrity structures'''<br />
 +
<nowiki>PSUM:
 +
for (PSUM = i = 0; i < Number_of_polygons; ++i)
 +
  for (j = 0; j < Polygon[i].Number_of_vertice; ++j)
 +
    PSUM += Polygon[i].Vertex[j].x + Polygon[i].Vertex[j].y;
 +
 
 +
OSUM:
 +
for (OSUM = i = 0; i < Number_of_objects; ++i)
 +
  OSUM += Object[i].x + Object[i].y + Object[i].type;
 +
 
 +
PICSUM:
 +
for (PICSUM = i = 0; i < Number_of_pictures; ++i)
 +
  PICSUM += Picture[i].x + Picture[i].y;
 +
 
 +
SUM = (PSUM + OSUM + PICSUM) * 3247.764325643;
 +
 
 +
Integrity 1 = SUM;
 +
 
 +
Integrity 2 = ((double) (rand() % 5871)) + 11877 - SUM;
 +
if (shareware)
 +
  Integrity 2 = ((double) (rand() % 4982)) + 20961 - SUM;
 +
 
 +
Integrity 3 = ((double) (rand() % 5871)) + 11877 - SUM;
 +
if (topology errors in lev)
 +
  Integrity 3 = ((double) (rand() % 4982)) + 20961 - SUM;
 +
 
 +
Integrity 4 = ((double) (rand() % 6102)) + 12112 - SUM;
 +
if (locked)
 +
  Integrity 4 = ((double) (rand() % 6310)) + 23090 - SUM;</nowiki>
 +
 
  
 
==== Language Examples ====
 
==== Language Examples ====
 
===== C/C++ =====
 
===== C/C++ =====
<nowiki>wow test</nowiki>
+
 
 
===== Python =====
 
===== Python =====
<nowiki>wow test</nowiki>
+
There is already a nice [https://github.com/sigvef/elma Elma Python library] by sigvef that should cover most of your needs. If not, there's some nice code examples for how you can unpack/pack files if you look at the source code.
 +
 
 
===== Rust =====
 
===== Rust =====
<nowiki>wow test</nowiki>
+
Full Elma read and write support for both levels and replays in this [https://github.com/hexjelly/elma-rust Elma Rust crate] by skint.
  
 
=== Replay files ===
 
=== Replay files ===
 +
 +
TODO: put stuff here
 +
 +
=== LGR files ===
 +
 +
See [[LGR/Technical]]
 +
 +
Some additional information is contained in the [[Big Book of Elma Facts]]
 +
 +
=== APIs ===
 +
Currently there are two online APIs available for public use.
 +
==== Elma Online API 2.0 ====
 +
Official API for the Elma Online site. Use this API if you want to get best times for a certain level.
 +
 +
==== Battle API ====
 +
Domovoy and b0ne's API that gives you current battle information in either XML or JSON format, and also calls to start levels.
 +
 +
===== Get battle information =====
 +
URLs are http://elma.seamy.ru:8880/current_battle for XML, http://elma.seamy.ru:8880/current_battle?json=1 for JSON<br />
 +
There's currently no direct way to get level information like the map and non-battle times. You'll unfortunately need to fetch the battle URL by the ID field and parse the HTML data to get the level ID from there. Then either use the official EOL API to get best times, or construct the map URL from the level ID.
 +
 +
Map URL examples:<br />
 +
http://elmaonline.net/images/map/346023/ No parameters gives you a 330px max X-resolution picture.<br />
 +
http://elmaonline.net/images/map/346023/1800/ To get 1810px max X-resolution picture. '''X-resolution adds 10px'''.<br />
 +
<del>http://elmaonline.net/images/map/346023/0/1800/ To get 1832px max Y-resolution picture.</del> No way to restrain Y-resolution independently? '''Y-resolution adds 32px'''.<br />
 +
If you add both restraints it will '''only''' use the highest axis for scaling and ignore the other axis' restraint.<br />
 +
The lowest resolution it will scale the axes to is 287; 297px actual for the X-axis, and 319px actual for the Y-axis.<br />
 +
Maximum resolution '''seems''' completely arbitrary and changes from level to level somehow. Some levels you can have in the 7000-7200px range, others stop at 5000 or so. Further information would be useful.
 +
 +
 +
{| class="wikitable"
 +
! Field || Context
 +
|-
 +
| designer || EOL Name of player. Example URL: http://elmaonline.net/players/DIvAn
 +
|-
 +
| file_name || Level file name. As there can be multiple levels with the same name, use the '''id''' field instead if you want to fetch the level information.
 +
|-
 +
| start_delta || Seconds elapsed from battle start. Given in negative values, so just add it to '''duration''' field to get time left.
 +
|-
 +
| battle_type || Battle type:<br />0: Normal<br />1: One life<br />2: First finish<br />3: Slowness<br />4: Survivor<br />5: Last result<br />6: Finish count<br />7: 1 Hour TT<br />8: Flag tag<br />9: Apple<br />10: Speed
 +
|-
 +
| battle_attrs || Bitmask for battle attributes:<br />1: See others<br />2: See times<br />4: Allow starter<br />8: Apple bug<br />16: No volt<br />32: No turn<br />64: One turn<br />128: No brake<br />256: No throttle<br />512: Always throttle<br />1024: Drunk<br />2048: No significance, ignore. No explanation, ask mila<br />4096: One-wheel<br />8192: Multiplayer
 +
|-
 +
| duration || Total battle time in seconds. Use '''start_delta''' field to get remaining time.
 +
|-
 +
| id || Battle ID. Example URL: http://elmaonline.net/battles/104896<br />
 +
|}
 +
 +
 +
<nowiki>http://elma.seamy.ru:8880/current_battle XML example result:
 +
 +
<CurrentBattle>
 +
  <designer>DIvAn</designer>
 +
  <file_name>D339.lev</file_name>
 +
  <start_delta>-175</start_delta>
 +
  <battle_type>0</battle_type>
 +
  <battle_attrs>2050</battle_attrs>
 +
  <duration>420</duration>
 +
  <id>104896</id>
 +
</CurrentBattle>
 +
 +
 +
http://elma.seamy.ru:8880/current_battle?json=1 JSON example result:
 +
 +
{
 +
  "designer":"DIvAn",
 +
  "file_name":"D339.lev",
 +
  "start_delta":-187,
 +
  "battle_type":0,
 +
  "battle_attrs":2050,
 +
  "duration":420,
 +
  "id":104896
 +
}
 +
</nowiki>
 +
 +
===== Start levels =====
 +
 +
Put stuff here.
 +
 +
=== Big Book of Elma Facts ===
 +
 +
See [[Big Book of Elma Facts]]
  
 
== Action SuperCross ==
 
== Action SuperCross ==
 
=== Levels ===
 
=== Levels ===
 
=== Replay files ===
 
=== Replay files ===
 +
 +
== Elma2 ==
 +
See [[Elma2/Technical]]

Latest revision as of 16:17, 1 December 2022

Technical information for Elasto Mania files, available APIs and various development stuff.

Elasto Mania

Note: All Elasto Mania files are in little-endian format.

The type name will depend on what programming language you are working with, here's a short equivalency list between the most common ones as a reference:

Bytes C Python¹ Rust
1 char c u8/i8²
1 unsigned char B u8
2 short h i16
4 int i i32
4 unsigned int I u32
4 float f f32
8 double d f64

¹ Using struct formatting [1]
² Depends on context. Assume that all chars in Elma files are ASCII and never expected to be signed, use u8. Probably?

Levels

How to parse a level byte by byte. Remember little-endianess.

Offset Type Bytes Description
0 char[5] 5 "POT14" for Elma levels. Across files start with "POT06" (See Across section).
5 short 2 Lower short of int value below. No particular use for anything?
7 int 4 Random number to link replays to this version of the level. Editing a level will change this.
11 double 8 Integrity check 1 (See section below table for calculations).
19 double 8 Integrity check 2 (See section below table for calculations).
27 double 8 Integrity check 3 (See section below table for calculations).
35 double 8 Integrity check 4, different for locked levels (See section below table for calculations).
43 char[51] 51 Level name.
94 char[16] 16 LGR name.
110 char[10] 10 Ground texture name.
120 char[10] 10 Sky texture name.
130 double 8 Number of polygons + 0.4643643. Maximum 300 for normal Elma, 1000 for EOL.
138 polygon polys * 24 See polygon section below table for structure.
? double 8 Number of objects + 0.4643643. Maximum 52 for normal Elma, 252 for EOL.
? object objs * 28 See object section below table for structure.
? double 8 Number of pictures + 0.2345672. Maximum 5000.
? picture pics * 54 See picture section below table for structure.
? int 4 0x3A106700 (0x0067103A) Optional: Marker for beginning of encrypted data
? top10 344 Encrypted single-player top10 list. See top10 section below table for structure.
? top10 344 Encrypted multi-player top10 list. See top10 section below table for structure.
? int 4 0x525D8400 (0x00845D52) Marker for end of encrypted data


Polygon structure
Type Bytes Description
int 4 Grass flag. 0 for normal polygon, 1 for grass.
int 4 Number of vertices.
vertex (double * 2) vertices * 16 X and Y as two doubles.


Object structure
Type Bytes Description
double 8 X position.
double 8 Y position.
int 4 Object type.
1: Flower
2: Apple
3: Killer
4: Start
int 4 Gravity (if object is apple).
0: Normal
1: Up
2: Down
3: Left
4: Right
int 4 Animation (if object is apple) - 1. E.g. animation numbers 1 to 9 in editor will be 0-8.


Picture structure
Type Bytes Description
char*10 10 Normal picture name.
char*10 10 Texture name.
char*10 10 Mask name.
double 8 X position.
double 8 Y position.
int 4 Z-distance, 1-999.
int 4 Clipping, 0 = Unclipped, 1 = Ground, 2 = Sky.


Top10 structure
Data needs to be decrypted first. See algorithm below table.
Type Bytes Description
int 4 Number of times in top10 list.
int*10 40 Array with times, in hundredths.
char*10*15 150 Array with player 1 names, each 15 chars.
char*10*15 150 Array with player 2 names, each 15 chars.

This structure is for one top 10 list.
You need to read both top10 lists (688 bytes) and decrypt them together with the following algorithm:

char *buf = TOP10S; // the 688 bytes of data
short ebp8 = 0x15;
short ebp10 = 0x2637;
for (int x = 0; x < 688; x++) {
  buf[x] ^= ebp8 & 0xFF; // note the bitwise operations
  ebp10 += (ebp8 % 0xD3D) * 0xD3D;
  ebp8 = ebp10 * 0x1F + 0xD3D;
}

You can use the exact algorithm for encrypting the top10 lists back if you're creating a level or editing lists as well.


Integrity structures

PSUM:
for (PSUM = i = 0; i < Number_of_polygons; ++i)
  for (j = 0; j < Polygon[i].Number_of_vertice; ++j)
    PSUM += Polygon[i].Vertex[j].x + Polygon[i].Vertex[j].y;

OSUM:
for (OSUM = i = 0; i < Number_of_objects; ++i)
  OSUM += Object[i].x + Object[i].y + Object[i].type;

PICSUM:
for (PICSUM = i = 0; i < Number_of_pictures; ++i)
  PICSUM += Picture[i].x + Picture[i].y;

SUM = (PSUM + OSUM + PICSUM) * 3247.764325643;

Integrity 1 = SUM;

Integrity 2 = ((double) (rand() % 5871)) + 11877 - SUM;
if (shareware)
  Integrity 2 = ((double) (rand() % 4982)) + 20961 - SUM;

Integrity 3 = ((double) (rand() % 5871)) + 11877 - SUM;
if (topology errors in lev)
  Integrity 3 = ((double) (rand() % 4982)) + 20961 - SUM;

Integrity 4 = ((double) (rand() % 6102)) + 12112 - SUM;
if (locked)
  Integrity 4 = ((double) (rand() % 6310)) + 23090 - SUM;


Language Examples

C/C++
Python

There is already a nice Elma Python library by sigvef that should cover most of your needs. If not, there's some nice code examples for how you can unpack/pack files if you look at the source code.

Rust

Full Elma read and write support for both levels and replays in this Elma Rust crate by skint.

Replay files

TODO: put stuff here

LGR files

See LGR/Technical

Some additional information is contained in the Big Book of Elma Facts

APIs

Currently there are two online APIs available for public use.

Elma Online API 2.0

Official API for the Elma Online site. Use this API if you want to get best times for a certain level.

Battle API

Domovoy and b0ne's API that gives you current battle information in either XML or JSON format, and also calls to start levels.

Get battle information

URLs are http://elma.seamy.ru:8880/current_battle for XML, http://elma.seamy.ru:8880/current_battle?json=1 for JSON
There's currently no direct way to get level information like the map and non-battle times. You'll unfortunately need to fetch the battle URL by the ID field and parse the HTML data to get the level ID from there. Then either use the official EOL API to get best times, or construct the map URL from the level ID.

Map URL examples:
http://elmaonline.net/images/map/346023/ No parameters gives you a 330px max X-resolution picture.
http://elmaonline.net/images/map/346023/1800/ To get 1810px max X-resolution picture. X-resolution adds 10px.
http://elmaonline.net/images/map/346023/0/1800/ To get 1832px max Y-resolution picture. No way to restrain Y-resolution independently? Y-resolution adds 32px.
If you add both restraints it will only use the highest axis for scaling and ignore the other axis' restraint.
The lowest resolution it will scale the axes to is 287; 297px actual for the X-axis, and 319px actual for the Y-axis.
Maximum resolution seems completely arbitrary and changes from level to level somehow. Some levels you can have in the 7000-7200px range, others stop at 5000 or so. Further information would be useful.


Field Context
designer EOL Name of player. Example URL: http://elmaonline.net/players/DIvAn
file_name Level file name. As there can be multiple levels with the same name, use the id field instead if you want to fetch the level information.
start_delta Seconds elapsed from battle start. Given in negative values, so just add it to duration field to get time left.
battle_type Battle type:
0: Normal
1: One life
2: First finish
3: Slowness
4: Survivor
5: Last result
6: Finish count
7: 1 Hour TT
8: Flag tag
9: Apple
10: Speed
battle_attrs Bitmask for battle attributes:
1: See others
2: See times
4: Allow starter
8: Apple bug
16: No volt
32: No turn
64: One turn
128: No brake
256: No throttle
512: Always throttle
1024: Drunk
2048: No significance, ignore. No explanation, ask mila
4096: One-wheel
8192: Multiplayer
duration Total battle time in seconds. Use start_delta field to get remaining time.
id Battle ID. Example URL: http://elmaonline.net/battles/104896


http://elma.seamy.ru:8880/current_battle XML example result:
 
 <CurrentBattle>
   <designer>DIvAn</designer>
   <file_name>D339.lev</file_name>
   <start_delta>-175</start_delta>
   <battle_type>0</battle_type>
   <battle_attrs>2050</battle_attrs>
   <duration>420</duration>
   <id>104896</id>
 </CurrentBattle>
 

http://elma.seamy.ru:8880/current_battle?json=1 JSON example result:

 { 
   "designer":"DIvAn",
   "file_name":"D339.lev",
   "start_delta":-187,
   "battle_type":0,
   "battle_attrs":2050,
   "duration":420,
   "id":104896
 }
 
Start levels

Put stuff here.

Big Book of Elma Facts

See Big Book of Elma Facts

Action SuperCross

Levels

Replay files

Elma2

See Elma2/Technical