New File Layout[]
0: Enabled Flag 1: Available Slots (could be removed) 2: Action List initialized flag 3: Learning Skill ID 4: Learning Skill Progress 5: Number of known actions 6: Ignore MP Flag 7: Temp Pref. Ranged 8: Unused 9: Unused 10-24: Entity 1-15 25-39: Condition 1-15 40-54: Comparator 1-15 55-69: Value 1-15 70-84: Action 1-15 85-104: Known Action IDs 105-124: Fatigue for known action IDs
Elona Save Format[]
The .s1 files contained as part of Elona saves are gzip'd binary files. This is possibly (probably) true for the other files as well, but I haven't investigated them.
- gdata.s1: This file is simply a list of 32-bit signed integer values stored in little-endian format. In the 1.43 source, data from this file is referenced under var_64(x), where x corresponds to the number of the entry, located at offset x*4.
To add a file to Elona's save (Integer Based):[]
- Define the variable it will be stored in, and give it dimensions. Dim instruction is used to set array size. Example:
- dim TestData, 115, 15
- This creates a 2D array named TestData that is 15 sets of 115 values. Good place to put this is just after the line containing "dim var_57".
- Find the line containing "ctrlFile:game". This is the beginning of a loop that loads static save data. Increment the loop counter (e.g. from the default "repeat 15" to "repeat 16").
- Under the last "If ( cnt == x )" section, add a new section, with cnt being equal to the max loop counter - 1. Example section:
if ( cnt == 15 ) { var_428 += "testdata.s1" var_1960 = 3680 }
- var_428 (or whatever the variable is in the latest decompilation - don't assume this variable name is current) already holds the save folder data. This just appends the file name to it.
- var_1960 is the size, in bytes, of the file. For one dimensional arrays, this is just size * 4. For two-dimensional arrays, this is sizeX * sizeY + 17 * sizeX
- Now we need to tell the game to actually save/load these values. First find the last zClose before the loop counter and add a section like this:
if ( cnt == 15 ) { if ( var_856 == 0 ) { zWrite TestData, var_1955, var_1960 } if ( var_856 == 1 ) { zRead TestData, var_1955, var_1960 } }
- This tells the game to load/save this file when saving the game (and creation upon a new game).
- (Optional) In the section that looks like this: "if ( cnt != 14 | var_64(96) >= 1200 ) {", modify the check against the cnt variable to something like "cnt < 14". This will allow the game to create a new, empty file for characters that have already been generated Otherwise, the game will crash.
Menu Creation (Choice Based)[]
"Choice Based" menus are the menus that display no text above them - like the (i)nteraction menu, the exit menu, and the living weapon menu.
There are five important parts to creating this type of menu.
- var_1095 - Holds the number of available choices. Can be set beforehand if the number of choices is static, or incremented if choices are dynamic.
- var_426(0, choice#) - This holds the string to display, the shortcut key, and the return value (as a string)
- var_371 - holds the game window x/y values, the x value of the menu in pixels (y value is calculated), and the "style".
- label_2452 - handles drawing the window and handles execution while the window is open.
- var_383 - holds the return value of the option that was chosen.
So, the code for an example window might look like this:
*TestMenu var_426(0, 0) = "Hello, World!", "a", "0" var_426(0, 1) = "Testing 123!", "b", "1" var_1095 = 2 var_371 = var_1096, var_1097, 600, 1 gosub *label_2452 if (var_383 == 0){ mes "Hello to you too!" } else { mes "Test successful!" } return
Elosnack Values[]
Multiply these by 4 to get the elosnack offset.
g.data:
- 8 = ?
- 9 = ? Possibly unused. Only referenced twice, and value never checked.
- 13 = hour?
- 14 = minute?
- 15 = related to quest timer somehow
- 16 = something related to containers? Total number of accessible container spaces? Increases by one if you get a "new" cooler and sets some property of the cooler to it. Do separate coolers access separate spaces?
- 18 = Time left on current weather effect
- 19 = previous map? used in the "You left <x>." messages.
- 545 = Fron can spawn (0), Fron can't spawn (1). Confirmed.
- 84 = quest timer? (min left for quest)
De-obfuscated Functions[]
- Function dmghp (WIP - 1.43)
dmghp(Target, RawDamage, Attacker, AttackElement = 0, RestoreValue = 0) { If(Target.IsAlive() == False) return; CalculatedDamage = RawDamage * (1 + Target.HasStatus(Berserk)); If(Attacker.Exists()) If(Attacker.HasStatus(Browbeat)) CalculatedDamage = CalculatedDamage/2; If (AttackElement.Exists() && AttackElement.Type != Physical) { TargetResistLevel = Target.ResistVal(AttackElement) / 50; if (TargetResistLevel < 3) CalculatedDamage = CalculatedDamage * 150 / limit(TargetResistLevel * 50 + 50, 40, 150); else CalculatedDamage = CalculatedDamage * 100 / (TargetResistLevel * 50 + 50); CalculatedDamage = CalculatedDamage * 100 / (Target.ResistVal(Magic) / 2 + 50); } ...
- Label_2533 (Shop Item Generation - Partially decoded, with notes)
Label_2533 /* Shop ID's 1001 - Armory 1006 - General Store 1004 - Magic Store 1008 - Goods Store 1007 - Blackmarket 1018 - Souvenir 1017 - Dye 1010 - Wandering Vendor 2003 - house visiting merchant? 1016 - Garolt/Mirok. Whichever one it is that sells stuff. 1023 - Arcbelc, music ticket vendor? 1024 - Unknown. One in Melkawn. Monster Ball vendor? 1019 - Sister merchant in mansion of little sister 1020 - Spell Writer (Mage's Guild) 1015 - Moyer 1003 - Bakery 1002 - Food (includes Aile) */ /* Item Categories 0 Base 10000 Weapon 12000 Helm 14000 Shield 16000 Armor 18000 Boots 19000 Girlde 20000 Cloak 22000 Glove 24000 Range 25000 Ammo 32000 Ring 34000 Amulet 53000 Scroll 54000 Spellbook 55000 Book 56000 Staff 57000 Food 59000 Tool 60000 Furniture 60001 Well 64000 Junk 77000 Ore 80000 Tree 92000 Trade */ /* Other Details var_574 is used in the item create function. Overrides var_573. */ //Presumably the following code clears the inventory of the vendor inv_getheader -1 repeat var_630, var_629 var_99(0, cnt) = 0 loop //Temporary name until I know what this does ShopFactor = 20 + Vendor.ShopRank() / 2 if ( ShopFactor > 80 ) { ShopFactor = 80 } if ( Vendor.ShopID() == "blackmarket" ) { ShopFactor = 5 + limit(Vendor.ShopRank() / 10, 1, 20) } if ( Vendor.ShopID() == "armory" ) { ShopFactor = 10 + limit(Vendor.ShopRank() / 10, 1, 90) } if ( Vendor.ShopID() == "wandering vendor" ) { ShopFactor = 4 + rnd(4) } if ( Vendor.ShopID() == 2003 ) { ShopFactor = 4 + rnd(4) } if ( Vendor.ShopID() == "Garok" ) { ShopFactor = 20 } if ( Vendor.ShopID() == "Souvenir" ) { ShopFactor /= 2 } if ( Vendor.ShopID() == "Music Ticket" ) { ShopFactor = 15 } if ( Vendor.ShopID() == 1024 ) { ShopFactor /= 4 } //repeat the following loop ShopFactor times repeat var_354 var_59 = 0 if ( Vendor.ShopID() == "Spell Writer" ) { var_635 = 0 // Loops 999 times. repeat 999 if ( var_434(2, cnt) > 1 ) { var_421(0, var_635) = cnt var_635++ } loop //End loop if ( var_635 == 0 ) { return } var_59 = var_421(0, rnd(var_635)) } if ( Vendor.ShopID() == "general store" ) { var_354 = rnd(5) if ( var_354 == 0 ) { var_573 = 25000 //Ammo } if ( var_354 == 1 ) { var_573 = 60000 //Furniture } if ( var_354 == 2 ) { var_573 = 20000 //Cloaks } if ( var_354 == 3 ) { var_573 = 77000 //Ore } if ( var_354 == 4 ) { var_573 = 59000 //Tools } if ( rnd(20) == 0 ) { var_59 = 734 //Gamble Chest } if ( rnd(8) == 0 ) { var_573 = 91000 //Cargo Rations } if ( rnd(10) == 0 ) { var_59 = var_1315(rnd(length(var_1315))) } if ( var_64(11) == 2 ) { if ( rnd(2) == 0 ) { var_59 = 907 //Chocolate Kit } } } if ( Vendor.ShopID() == "Bakery" ) { if ( rnd(3) != 0 ) { var_574 = 57001 //Flour Products } else { var_574 = 57002 //Noodle Products } } if ( Vendor.ShopID() == "Food" ) { if ( var_57(27, var_243) != 534 ) { // If the food vendor is not Aile if ( rnd(3) != 0 ) { continue } } if ( var_57(27, var_243) == 534 ) { // If the food vendor is Aile if ( rnd(5) != 0 ) { continue } } var_573 = 57000 //Food if ( rnd(5) == 0 ) { var_573 = 91000 // Cargo Rations } if ( var_57(27, var_243) == 534 ) { // If Aile if ( rnd(5) == 0 ) { var_59 = 910 //Miniature Blimp } if ( rnd(10) == 0 ) { var_59 = 520 //Wing } } } if ( Vendor.ShopID() == 1010 | Vendor.ShopID() == 2003 ) { var_573 = var_580(rnd(length(var_580))) var_576 = 3 if ( rnd(2) == 0 ) { var_576 = 4 } } if ( Vendor.ShopID() == 1005 ) { var_573 = 91000 if ( rnd(4) ) { var_574 = 52002 } if ( rnd(20) == 0 ) { var_59 = 734 } } if ( Vendor.ShopID() == "goods store" ) { var_573 = 56000 if ( rnd(3) == 0 ) { var_573 = var_580(rnd(length(var_580))) } if ( rnd(3) == 0 ) { var_573 = 60000 } if ( rnd(5) == 0 ) { var_573 = 57000 } if ( rnd(4) == 0 ) { var_573 = 53000 } if ( rnd(15) == 0 ) { var_573 = 55000 } if ( rnd(10) == 0 ) { var_573 = 91000 } if ( rnd(10) == 0 ) { var_59 = var_1315(rnd(length(var_1315))) } if ( rnd(15) == 0 ) { var_59 = 511 } } if ( Vendor.ShopID() == "armory" ) { if ( rnd(limit(Vendor.ShopRank(), 1, 1000) + 200) > rnd(1000) ) { var_576 = 3 } var_354 = rnd(6) if ( var_354 == 0 ) { var_573 = 16000 } if ( var_354 == 1 ) { var_573 = 12000 } if ( var_354 == 2 ) { var_573 = 22000 } if ( var_354 == 3 ) { var_573 = 18000 } if ( var_354 == 4 ) { var_573 = 14000 } if ( var_354 == 5 ) { var_573 = 19000 } if ( rnd(3) == 0 ) { if ( rnd(3) == 0 ) { var_573 = 10000 } else { var_573 = 24000 } } } if ( Vendor.ShopID() == 1009 ) { var_573 = 92000 } if ( Vendor.ShopID() == 1021 ) { var_573 = 59000 if ( rnd(2) == 0 ) { var_59 = 636 } if ( rnd(2) == 0 ) { var_59 = 629 } } if ( Vendor.ShopID() == 1011 ) { if ( rnd(4) == 0 ) { var_573 = 24000 } if ( rnd(5) == 0 ) { var_573 = 25000 } if ( rnd(3) == 0 ) { var_573 = 57000 } fltn "sf" } if ( Vendor.ShopID() == 1013 ) { if ( rnd(3) != 0 ) { continue } var_573 = 55000 if ( rnd(3) ) { var_59 = var_1315(rnd(length(var_1315))) } if ( rnd(5) == 0 ) { var_59 = 511 } if ( rnd(5) == 0 ) { var_59 = 897 } if ( rnd(20) == 0 ) { var_59 = 712 } } if ( Vendor.ShopID() == "Souvenir" ) { fltn "spshop" if ( rnd(20) == 0 ) { var_59 = 869 } } if ( Vendor.ShopID() == 1022 ) { fltn "fest" if ( rnd(12) == 0 ) { var_59 = 750 } if ( rnd(12) == 0 ) { var_59 = 751 } if ( rnd(5) == 0 ) { var_59 = 770 } if ( rnd(5) == 0 ) { var_59 = 867 } if ( var_64(20) == 33 ) { if ( rnd(12) == 0 ) { var_59 = 762 } } if ( var_64(20) == 33 ) { if ( rnd(12) == 0 ) { var_59 = 768 } } if ( var_64(20) == 33 ) { if ( rnd(12) == 0 ) { var_59 = 769 } } if ( var_64(20) == 33 ) { if ( rnd(12) == 0 ) { var_59 = 779 } } if ( var_64(20) == 33 ) { if ( rnd(12) == 0 ) { var_59 = 780 } } if ( var_64(20) == 57 ) { if ( rnd(12) == 0 ) { var_59 = 759 } } if ( var_64(20) == 57 ) { if ( rnd(12) == 0 ) { var_59 = 512 } } if ( var_64(20) == 57 ) { if ( rnd(12) == 0 ) { var_59 = 873 } } if ( var_64(20) == 57 ) { if ( rnd(12) == 0 ) { var_59 = 872 } } if ( var_64(20) == 57 ) { if ( rnd(5) == 0 ) { var_59 = 844 } } } if ( Vendor.ShopID() == 1024 ) { var_59 = 685 } if ( Vendor.ShopID() == "Dye" ) { var_59 = 519 } if ( Vendor.ShopID() == 1012 ) { var_573 = 60000 if ( cnt == 0 ) { var_59 = 510 } if ( cnt == 1 ) { var_59 = 561 } if ( cnt == 2 ) { var_59 = 562 } if ( cnt == 3 ) { var_59 = 547 } if ( cnt == 4 ) { var_59 = 579 } if ( cnt == 5 ) { var_59 = 576 } if ( cnt == 6 ) { var_59 = 611 } if ( cnt == 7 ) { var_59 = 701 } if ( cnt > 10 ) { if ( rnd(3) != 0 ) { continue } } if ( cnt == 19 ) { var_59 = 413 } if ( cnt == 20 ) { var_59 = 414 } if ( cnt == 21 ) { var_59 = 616 } } if ( Vendor.ShopID() == 1014 ) { var_59 = 617 } if ( Vendor.ShopID() == 1016 ) { if ( cnt == 0 ) { var_59 = 430 } if ( cnt == 1 ) { var_59 = 431 } if ( cnt == 2 ) { var_59 = 502 } if ( cnt == 3 ) { var_59 = 480 } if ( cnt == 4 ) { var_59 = 421 } if ( cnt == 5 ) { var_59 = 603 } if ( cnt == 6 ) { var_59 = 615 } if ( cnt == 7 ) { var_59 = 559 } if ( cnt == 8 ) { var_59 = 516 } if ( cnt == 9 ) { var_59 = 616 } if ( cnt == 10 ) { var_59 = 623 } if ( cnt == 11 ) { var_59 = 505 } if ( cnt == 12 ) { var_59 = 624 } if ( cnt == 13 ) { var_59 = 625 } if ( cnt == 14 ) { var_59 = 626 } if ( cnt == 15 ) { var_59 = 627 } if ( cnt == 16 ) { var_59 = 56 } if ( cnt == 17 ) { var_59 = 742 } if ( cnt == 18 ) { var_59 = 760 } if ( cnt == 19 ) { var_59 = 892 } if ( cnt > 19 ) { continue } } if ( Vendor.ShopID() == 1023 ) { if ( cnt == 0 ) { var_59 = 799 } if ( cnt == 1 ) { var_59 = 427 } if ( cnt == 2 ) { var_59 = 425 } if ( cnt == 3 ) { var_59 = 426 } if ( cnt == 4 ) { var_59 = 424 } if ( cnt == 5 ) { var_59 = 423 } if ( cnt == 6 ) { var_59 = 422 } if ( cnt == 7 ) { var_59 = 809 } if ( cnt == 8 ) { var_59 = 811 } if ( cnt == 9 ) { var_59 = 847 } if ( cnt == 10 ) { var_59 = 816 } if ( cnt == 11 ) { var_59 = 655 } if ( cnt == 12 ) { var_59 = 662 } if ( cnt == 13 ) { var_59 = 639 } if ( cnt > 13 ) { continue } } itemcreate -1, var_59, -1, -1, 0 if ( stat == 0 ) { break } if ( Vendor.ShopID() == 1016 ) { var_99(0, var_514) = 1 var_99(17, var_514) = 0 if ( var_99(3, var_514) == 480 ) { var_99(9, var_514) = 4 } continue } if ( Vendor.ShopID() == 1023 ) { var_99(0, var_514) = 1 var_99(17, var_514) = 0 continue } var_1246 = 0 if ( instr(var_476(var_99(3, var_514)), 0, "/neg/") != (-1) ) { var_1246 = 1 } if ( instr(var_476(var_99(3, var_514)), 0, "/noshop/") != (-1) ) { if ( Vendor.ShopID() != Souvenir ) { var_1246 = 1 } } if ( var_1246 ) { var_99(0, var_514) = 0 continue } if ( Vendor.ShopID() == 1012 ) { var_573 = 60000 } gosub *label_2534 var_99(0, var_514) = rnd(var_381) + 1 if ( Vendor.ShopID() == 1003 ) { if ( rnd(5) <= 2 ) { var_99(0, var_514) = limit(rnd(Vendor.ShopRank() + 1) / 30 + 1, 1, 3) } } if ( Vendor.ShopID() == 1009 ) { var_354 = var_483(var_99(25, var_514)) if ( var_354 <= 70 ) { var_99(0, var_514) = var_99(0, var_514) * 200 / 100 } if ( var_354 <= 50 ) { var_99(0, var_514) = var_99(0, var_514) * 200 / 100 } if ( var_354 >= 80 ) { var_99(0, var_514) = var_99(0, var_514) / 2 + 1 if ( rnd(2) ) { var_99(0, var_514) = 0 continue } } if ( var_354 >= 100 ) { var_99(0, var_514) = var_99(0, var_514) / 2 + 1 if ( rnd(3) ) { var_99(0, var_514) = 0 continue } } var_99(0, var_514) = var_99(0, var_514) * (100 + Vendor.ShopRank()) / 100 + 1 } var_354 = refitem(var_99(3, var_514), 5) if ( var_99(17, var_514) == (-1) | var_99(17, var_514) == (-2) ) { var_99(0, var_514) = 0 continue } if ( var_99(17, var_514) == 1 ) { var_99(0, var_514) = 1 } if ( var_354 == 52000 ) { if ( var_99(3, var_514) == 516 ) { var_99(0, var_514) = 0 } } if ( var_354 == 57000 ) { if ( refitem(var_99(3, var_514), 9) == 58500 ) { if ( rnd(5) ) { var_99(0, var_514) = 0 } } } if ( Vendor.ShopID() == Souvenir ) { var_99(1, var_514) = limit(var_99(1, var_514), 1, 1000000) * 50 if ( var_99(3, var_514) == 729 ) { var_99(1, var_514) *= 10 } } if ( Vendor.ShopID() == 1015 ) { var_99(1, var_514) *= 2 } if ( Vendor.ShopID() == 1007 ) { if ( var_64(250 + 15) != 0 ) { var_99(1, var_514) *= 2 } else { var_99(1, var_514) *= 3 } } if ( Vendor.ShopID() == 1010 ) { var_99(1, var_514) *= 2 } if ( Vendor.ShopID() == 2003 ) { var_99(1, var_514) = var_99(1, var_514) * 4 / 5 } if ( Vendor.ShopID() == 1020 ) { var_99(1, var_514) = var_99(1, var_514) * 3 / 2 if ( var_64(20) == 72 ) { var_99(1, var_514) = var_99(1, var_514) * 3 } } if ( Vendor.ShopID() == 1024 ) { var_99(1, var_514) *= 2 } loop //end loop by ShopFactor var_57(154, var_243) = var_64(13) + var_64(12) * 24 + var_64(11) * 24 * 30 + var_64(10) * 24 * 30 * 12 + 48 * (1 + (Vendor.ShopID() == 1009)) if ( Vendor.ShopID() == 1009 ) { var_57(154, var_243) = var_64(13) + var_64(12) * 24 + var_64(11) * 24 * 30 + var_64(10) * 24 * 30 * 12 + 168 } return