GEN
The lump with the extension .GEN is a critical one. It contains miscellaneous information about a wide variety of unrelated things, especially max number of records for other files, password information, and other stuff that I felt like sticking in there too-- some of which really don't belong.
New general options are increasingly being added to general.reld instead, the RELOAD-format alternative.
This lump is stored in BSAVE format. It has a 7 byte header which indicates the size of the remaining data (and other things). See BSAVE Header for more information.
The .gen lump is normally 1007 bytes long (including 7 byte header). NOTE: The fixWipeGEN fixbit must be checked when reading values at offsets 199 and up.
The following is the meaning of each element.
Grey fields are obsolete or unused. |
Pink fields are runtime-only fields used by GAME that do not really belong GEN in the first place, but are there anyway to make including them in SAV files more convenient, which is now a moot argument because of the conversion to RSAV. |
Blue fields are editor-only fields used by CUSTOM, although almost all of them are also used in-game for error-checking valid data record numbers. GAME and CUSTOM will both automatically set all these "max/number of X" fields to the true values. |
Green records are preserved in RSAV files. |
Element | Data | Constant | Meaning |
---|---|---|---|
0 | INT | genMaxMap | Max (last) valid map number |
1 | INT | genTitle | Title screen backdrop index |
2 | INT | genTitleMus | title music |
3 | INT | genVictMus | victory music + 1 (0 for none) |
4 | INT | genBatMus | default battle music |
5 | INT | genPassVersion | passcode format number |
6 | INT | genPW3Rot | old (third-style) passcode rotator |
7-15 | BYTE*17 + 1 | old (third-style) obfuscated passcode + 1 unused byte | |
16-25 | INT*10 | ancient (first-style) obsolete password data | |
26 | INT | genMaxHeroPic | Last (max) valid hero spriteset number (in PT0/heroes.rgfx) |
27 | INT | genMaxEnemy1Pic | Last (max) valid small enemy spriteset number (graphics in PT1/enemies.rgfx). Includes only the spritesets in enemies.rgfx which are counted as small enemy graphics. |
28 | INT | genMaxEnemy2Pic | Last (max) valid medium spriteset number (graphics in PT2/enemies.rgfx). Includes only the spritesets in enemies.rgfx which are counted as medium enemy graphics. |
29 | INT | genMaxEnemy3Pic | Last (max) valid large enemy spriteset number (in PT3/enemies.rgfx). Includes only the spritesets in enemies.rgfx which are counted as large enemy graphics. |
30 | INT | genMaxNPCPic | Last (max) valid walkabout spriteset number (in PT4/walkabouts.rgfx) |
31 | INT | genMaxWeaponPic | Last (max) valid weapon spriteset number (in PT5/weapons.rgfx) |
32 | INT | genMaxAttackPic | Last (max) valid attack spriteset number (in PT6/attacks.rgfx) |
33 | INT | genMaxTile | Last (max) tileset number in TIL |
34 | INT | genMaxAttack | max attack definitions in DT6 |
35 | INT | genMaxHero | max hero definitions in DT0 |
36 | INT | genMaxEnemy | max enemy definitions in DT1 |
37 | INT | genMaxFormation | max formations in FOR |
38 | INT | genMaxPal | max palettes in PAL |
39 | INT | genMaxTextbox | max text boxes in SAY |
40 | INT | genMaxPlotscript | total available plotscripts (number of records in PLOTSCR.LST) |
41 | INT | genNewgameScript | ID of new-game plotscript |
42 | INT | genGameoverScript | ID of game-over plotscript |
43 | INT | genMaxRegularScript | ID of highest manually numbered old-style plotscript |
44 | INT | genSuspendBits | suspendstuff bits: 0 - suspendnpcs |
45 | INT | genCamera | camera mode |
46 | INT | genCamArg1 | cameraarg1 |
47 | INT | genCamArg2 | cameraarg2 |
48 | INT | genCamArg3 | cameraarg3 |
49 | INT | genCamArg4 | cameraarg4 |
50 | INT | genScrBackdrop | Index of currently displaying script backdrop , or -1 for none. (Does not include textbox backdrops) |
51 | INT | genDays | days of play |
52 | INT | genHours | hours of play |
53 | INT | genMinutes | minutes of play |
54 | INT | genSeconds | seconds of play |
55 | INT | genMaxVehicle | max vehicle types in VEH |
56 | INT | genMaxTagname | Last named tag |
57 | INT | genLoadgameScript | load-game plotscript |
58 | INT | genTextboxBackdrop | Index of currently displaying text box backdrop |
59 | INT | genEnemyDissolve | Default enemy dissolve type. See dissolve types in DT1 (each dissolve type is numbered one less than it is in DT1, for example Random Pixels is 0. |
60 | INT | genJoy | Enable/disable joystick |
61 | INT | genPoisonChar | Posion status indicator char (default to 161 if 0) |
62 | INT | genStunChar | Stun status indicator char (default to 159 if 0) |
63 | INT | genDamageCap | Damage cap (0 if there is none) |
64 | INT | genMuteChar | Mute status indicator char (default to 163 if 0) |
65-76 | INT*12 | genStatCap | Stat caps for each stat, in this order: HP MP Atk Aim Def Dog Mag Wil Spd Ctr Focus Extra Hits |
77 | INT | genMaxSFX | Number of sound effects |
78 | INT | genMasterPal | Master palette number in PALETTES.BIN |
79 | INT | genMaxMasterPal | Max master palette number in PALETTES.BIN and number of UI color sets in UICOLORS.BIN |
80 | INT | genMaxMenu | Max menu in MENUS.BIN |
81 | INT | genMaxMenuItem | Max menu item in MENUITEM.BIN |
82 | INT | genMaxItem | Max item number in ITM |
83 | INT | genMaxBoxBorder | Max valid box border spriteset number (in PT7/boxborders.rgfx) |
84 | INT | genMaxPortrait | Max valid portrait spriteset number (in PT8/portraits.rgfx) |
85 | INT | genMaxInventory | Max available inventory slots. 599 is current default max. Gets rounded up to the next multiple of 3 slots (counting the zeroth slot). Zero uses the default max of 599 (600 counting zero) |
86 | INT | genErrorLevel | Error suppression level: 0: default to 4 2: show all warnings 3: suppress some warnings 4: suppress warnings from weak type checking 5: don't show errors not displayed in old versions 6: show only errors about corruption or interpreter problems |
87 | INT | genLevelCap | Maximum hero level (0-genMaxLevel) (Unlike genMaxLevel, this can be changed with scripting, and has no effect on stat growth) |
88 | INT | genEquipMergeFormula | Function used to calculate total hero elemental resistances from equipment: 0: Awful compatible formula |
89 | INT | genNumElements | Number of elements which the game uses, up to 64 (any higher numbered ones should be ignored in-game) |
90 | INT | genUnlockedReserveXP | Experience gained by unlocked reserve heroes, as a % of that given to active party heroes |
91 | INT | genLockedReserveXP | Experience gained by locked reserve heroes, as a % of that given to active party heroes |
92 | INT | genPW4Hash | Hashed password, 0 if none |
93 | INT | genPW2Offset | old(second-style) passcode offset |
94 | INT | genPW2Length | old(second-style) passcode length (-1 if there is no password) |
95 | INT | genVersion | RPG file format version ID (The latest version number is CURRENT_RPG_VERSION in const.bi. See also Incrementing the RPG format version number) |
96 | INT | genStartMoney | starting money |
97 | INT | genMaxShop | last shop in SHO |
98 | INT | genPW1Offset | old-old(first-style) passcode offset |
99 | INT | genPW1Length | old-old(first-style) passcode length |
100 | INT | genNumBackdrops | Number of backdrops (in MXS/backdrops.rgfx) |
101 | INT | genBits | #General bitsets 0-15 |
102 | INT | genStartX | starting X |
103 | INT | genStartY | starting Y |
104 | INT | genStartMap | starting Map |
105 | INT | genOneTimeNPC | one-time-NPC indexer |
106-170 | INT*65 | genOneTimeNPCBits | one-time-NPC placeholder bits. there are 0-1039 bits, but savegames store 0-1031 and the NPC editor only allows 0-1000 |
171 | INT | genDefaultDeathSfx | Default enemy death sound effect ID + 1 (0 for none) |
172 | INT | genMaxSong | last song number (SNG and BAM) |
173 | INT | genAcceptSfx | Sound effect to use for "accept" (in menus) ID + 1 (0 for none) |
174 | INT | genCancelSfx | Sound effect to use for "cancel" (in menus) ID + 1 (0 for none) |
175 | INT | genChooseSfx | Sound effect to use for moving the cursor (in menus) ID + 1 (0 for none) |
176 | INT | gentextboxLetter | Sound effect to use for displaying text box lines ID + 1 (0 for none) |
177-178 | INT*2 | genBits2 | #General bitsets 16-47 |
179 | INT | genItemLearnSFX | Sound effect when learning a spell from an item. ID + 1 (0 for none) |
180 | INT | genCantLearnSFX | Sound effect when a hero couldn't learn spell from item. ID + 1 (0 for none) |
181 | INT | genBuySFX | Sound effect when buying an item from a shop. ID + 1 (0 for none) |
182 | INT | genHireSFX | Sound effect when hiring a hero from a shop. ID + 1 (0 for none) |
183 | INT | genSellSFX | Sound effect when selling an item to a shop. ID + 1 (0 for none) |
184 | INT | genCantBuySFX | Sound effect when you can't afford item/hire. ID + 1 (0 for none) |
185 | INT | genCantSellSFX | Sound effect when trying to sell an unsellable item. ID + 1 (0 for none) |
186 | INT | genDamageDisplayTicks | Number of ticks to display damage numbers in battle (default 7) |
187 | INT | genDamageDisplayRise | Number of pixels the displayed damage should rise (default 14) |
188 | INT | genHeroWeakHP | % HP threshold for hero weak state (default to 20 if 0) |
189 | INT | genEnemyWeakHP | % HP threshold for enemy weak state (default to 20 if 0) |
190 | INT | genAutosortScheme | Method of autosorting inventory: 0 - by item type |
191 | INT | genMaxLevel | Maximum level (0-99) This is the unchanging maximum allowed level for heroes. it is used in the hero stats editor. (Not to be confused with genLevelCap) |
192 | INT | genBattleMode | Battle mode 0=Active-time, 1=Turn-based |
193 | INT | genItemStackSize | Default maximum size of an inventory stack |
194 | INT | genResolutionX | Width in pixels of the game screen (not including graphics backend scaling) or 0 for default, 320x200 |
195 | INT | genResolutionY | Height in pixels of the game screen (see above). |
196 | INT | genEscMenuScript | plotscript to run when pressing ESC or ALT to open the menu (if zero, just open the menu) |
197 | INT | genSaveSlotCount | The number of available save slots, 1 to 1000. If 0, the default of 4 will be used. In versions older than fufluns the valid range was 1-32 |
198 | INT | genMillisecPerFrame | Number of milliseconds per frame, or 55 if zero. |
199 | INT | genStealSuccessSFX | ID+1 of sound effect to play for a successful item steal attack, or zero for none. |
200 | INT | genStealFailSFX | ID+1 of sound effect to play for an unsuccessful item steal attack, or zero for none. |
201 | INT | genStealNoItemSFX | ID+1 of sound effect to play for an item steal attack with nothing to steal, or zero for none. |
202 | INT | genRegenChar | Regen status indicator char (default to 32 if 0). |
203 | INT | genDefaultScale | (Obsolete). For a short while, contained the default scale/zoom factor of the display (1-16), or zero for whatever default is suitable. |
204 | INT | genDebugMode | 0=Release mode, script errors hidden, 1=Debug mode, script errors shown. This setting is editable, and is used as the base for genCurrentDebugMode |
205 | INT | genCurrentDebugMode | 0=Release mode, script errors hidden, 1=Debug mode, script errors shown. This is not directly editable. It is set based on genDebugMode but may be temporarily modified by the "Distribute Game" menu or the "Test Game" feature |
206 | INT | genStartHero | ID number the initial hero when starting a new game. |
207 | INT | genStartTextbox | ID number a textbox to open when starting a new game, 0 means none. |
208 | INT | genWindowSize | Default window size about X% of screen, in multiples of 10% (1-10). 10 means maximize. If 0, default to 8. |
209 | INT | genLivePreviewWindowSize | Test-Game default window size about X% of screen, in multiples of 10%. 10 means maximize. If 0, default to 5. |
210 | INT | genFullscreen | If 1 (or nonzero), default to fullscreen if there is no remembered preference for this game from previous plays. |
211 | INT | genMusicVolume | Initial music volume as a percentage (Needs initialising if fixInitDefaultVolumes false) |
212 | INT | genSFXVolume | Initial global sound effects volume as a percentage (Needs initialising if fixInitDefaultVolumes false) |
213 | INT | genRungameFullscreenIndependent | If false, fullscreen settings/config for games spawned by rungame are ignored |
214 | INT | genSkipBattleRewardsTicks | If > 0 then the battle rewards messages will automatically advance after this many ticks |
215 | INT | genDefOnkeypressScript | Default on-keypress Plotscript Trigger |
216 | INT | genDefEachStepScript | Default each-step Plotscript Trigger |
217 | INT | genDefAfterBattleScript | Default after-battle Plotscript Trigger |
218 | INT | genDefInsteadOfBattleScript | Default instead-of-battle Plotscript Trigger |
219 | INT | genDefMapAutorunScript | Default map autorun Plotscript Trigger |
220 | INT | genMaxEnemyPic | Last (max) valid enemy spriteset number (in enemies.rgfx) |
221 | INT | genMinimapAlgorithm | Algorithm for producing in-game minimaps: 0 - Smooth scaling (default) |
222-225 | INT*4 | genBits3 | #General bitsets 48-111 |
226 | INT | genDefCounterProvoke | Default Provoke Counterattacks setting for attacks. Value 0-8, same meanings as DT6 Provoke Counterattacks setting, except Default means 'Always' |
227 | INT | genInventSlotx1Display | When to display x1 in inventory: 0: always, 1: never, 2: only if item maximum stacksize > 1 |
228 | INT | genCameraOnWalkaboutFocus | How to calculate walkabout camera focus: 0: on tile, 1: on sprite, 2: on sprite minus Z/offset |
229 | INT | genTicksPerWalkFrame | Number of ticks per walking frame. 0 = default (110ms/frame; ticks calculated from genMillisecPerFrame and rounded to a whole number) |
230 | INT | genAddHeroScript | Script trigger called when hero added |
231 | INT | genRemoveHeroScript | Script trigger called when hero deleted/removed |
232 | INT | genMoveHeroScript | Script trigger called when hero moved to a different slot |
233 | INT | gen8bitBlendAlgo | Algorithm to use for palette lookups for blending in 8-bit color mode: 0 - Dither (Half of pixels (in a chequered pattern) are nearest-matched, with 1/2 or 3/4 (alternating) of error diffused to next pixel) |
234 | INT | genPreviewBackdrop | 0 or ID+1 of backdrop to use for enemy previews (and maybe more in future) |
235 | INT | gen32bitMode | 1 for 32-bit color mode, 0 for 8-bit |
236 | INT | genDefaultBattleMenu | 0 for default, or menu ID + 1 |
237-499 | INT | unused space |
Maximums : "max XYZ" means 'the highest numbered XYZ'. If you have 80 enemies defined, then "max enemy" == 79, because the numbering starts from 0.
General bitsets[edit]
Preference bits, backcompat bits and battle system bits are stored in a single bit array, which is split into pieces beginning at gen(genBits), gen(genBits2) and gen(genBits3). They are accessed with the prefbit() and setprefbit() functions in FB code, and read preference bit and write preference bit functions in HS code.
Note: in the Preference Bitsets and other editors, some of the names of bits are reversed from what they are below. For example the editor has a bit named "Restore HP on level-up" which appears ON if bit 2 ("Don't restore HP on level-up") is OFF.
Also note that some bits are turned on during game upgrade by fixbits.
- 0 - Pause in battle on spell and item menus
- 1 - Enable Caterpillar party
- 2 - Don't restore HP on level-up
- 3 - Don't restore MP on level-up
- 4 - Inns don't revive dead heros
- 5 - Hero swapping always available
- 6 - Hide ready meter in battle
- 7 - Hide health bar in battle
- 8 - Disable debugging keys
- 9 - Simulate old level-up bug
- 10 - Permit double-triggering of scripts
- 11 - Skip title screen
- 12 - Skip load screen
- 13 - Pause in battle on all menus
- 14 - Disable Hero's Battle Cursor
- 15 - Default passability disabled by default
- 16 - Simulate pushable NPC collision bug
- 17 - Disable ESC to run from battle
- 18 - Don't save gameover/loadgame script IDs (actually prevents using the stored "game over" or "loadgame" script ID number in preference to the ones specified by gen(genGameoverScript) and gen(genLoadgameScript))
- 19 - Dead heroes gain share of experience
- 20 - Locked heros can't be rearranged
- 21 - Attack captions pause battle meters
- 22 - Don't randomize battle ready-meters
- 23 - Battle menus wait for attack animations
- 24 - Enable better scancodes for scripts
- 25 - Simulate old fail vs. elemental resist bit
- 26 - 0 damage when immune to attack elements
- 27 - Recreate map slices when changing maps
- 28 - Harm tiles harm non-caterpillar heroes
- 29 - Ignore extra Hits stat
- 30 - Don't divide experience between heroes
- 31 - Don't reset max stats after OOB attack
- 32 - Don't limit max tags to 1000
- 33 - Enable bug 430
- 34 - showtextbox happens immediately
- 35 - Pause battle while targeting attacks
- 36 - Old attack positioning at bottom-left of target
- 37 - Wrap map layers over edge of Crop maps
- 38 - Never show script timer during battles
- 39 - Draw Backdrop slice above Script layer
- 40 - Don't stop music when starting/loading game
- 41 - Keep caterpillar length the same when speed changes
- 42 - Heroes use Walk in Place animation while idle.
- 43 - Cap minimum stats at zero.
- 44 - Hide empty save slots at the bottom of the save/load menu
- 45 - Attack IDs in scripts are offset by +1 (This incomplete feature is not in nightlies)
- 46 - Negative damage harmtiles don't cure above max HP
- 47 - Don't map joystick controls to keyboard keys for scripts
- 48 - Disable ESC to cancel/change a hero's attack (turn-based battles only)
- 49 - Show hero MP meter
- 50 - Non-turn attack delays don't also cause turn delays
- 51 - Don't break Speed ties randomly (turn-based battles only)
- 52 - Ignore MP~ stat
- 53 - "!Map joystick (left) stick to dpad"
- 54 - Auto-targeted attacks in random spell lists respect attack costs
- 55 - Turncoats and defectors always treat self as an ally
- 56 - Battles don't display at 320x200
- 57 - Use old direction key tiebreaking
- 58 - Ignore key repeats for confirm, cancel, and menu
- 59 - Always allow key repeat for confirm in battles
- 60 - Hide battle menu while targeting (This is a WIP feature)
- Bits up to 111 unused
Passwords[edit]
The password protection in the RPG format is necessarily wet-paper-bag weak, due to the GNU GPL licensing of the engine. RPG password protection works entirely on the honor system. It's kinda like that faded sign hanging on the gates of Anhk-Morpork that says "Thank you for not invading our city"
Here is how to read the password. Please-please-pretty-please don't just ignore it. It is flimsy pathetic laughable protection, but its all we have! Dont take away our wire umbrella in the lightening storm!
Over time there have been 4 different password formats, referred to as PW1, PW2, PW3, PW4. All are stored in the GEN lump. The format is determined by looking at genVersion and genPassVersion (go down this table):
Format | Indicator | Introduced | Last used |
---|---|---|---|
PW4 | gen(genPassVersion) = 257 | pre-Zenzizenzic (Dec 28 2010) | Still in use. |
PW3 | gen(genPassVersion) = 256 | Ozarks (June 28 2004) | Still partial back-compat (see below) |
PW2 | gen(genVersion) >= 3 | June 18 1999 | Written until Dec 28 2010 |
PW1 | Otherwise | Stone Age |
For backwards compatibility, Custom also writes a blank PW3 password is there is no password, or a random PW3 password if there is one.
The easiest thing to do when decoding an RPG file that still has its password in an old format is to print a warning message telling the user to upgrade their RPG file with CUSTOM.EXE
PW4[edit]
This new format stores just a 9-bit hash of the password, in gen(genPW4Hash), so that it is not retrievable.
The hash is calculated as follows (or see common.bas for the FB version):
unsigned short passwordhash (char *p) { if (strlen(p) == 0) return 0; unsigned int hash = 0; while (*p) hash = hash * 3 + *p++ * 31; return (hash & 511) | 512; }
PW3[edit]
In this format, the password is obscured with weak, but space-wasting encryption, and the length of the password is totally unprotected.
GEN(6) is the password rotator. It will always be a random number from 1 to 254 that will be used to ascii-rotate/obfuscate the password.
GEN(7-15) is the password text. There are 17 bytes of data here (so the high byte of GEN(15) is unused). Read this string of 17 bytes. ascii-rotate each character by subtracting GEN(6) and if the result is < 0 then add 255, and then discard all characters with ascii values less than 32 (space). The resulting string will be your password. If the string is empty (all 17 chars were under 32) then the RPG file does not have a password.
PW2[edit]
This information describes the obsolete scattertable-based password storage format. It is only here as a curiosity... and a monument of shame to security-through-obscurity. The body of the scattertable lived at GEN(200) to GEN(359), with the "scattertable head" at gen(199). The first element of the password mess GEN(200) is the bit-offset of the first bit in the password, relative to the beginning of the scattertable head (199). The next element (201) is the bit-offset of the next bit in the password, and so on. So GEN(200) thru GEN(207) will give you the bits that make up the first ascii char of the password. GEN(208)-GEN(215) gives you the next char, and so on up to GEN(94)+1 bytes. After you have read all the chars from the scattertable, rotate them down by the value of GEN(93) (that is to say, subtract GEN(93) from the ascii value of each char, and if the result is less than 0, add 256).
Clear as mud, right? Here is an example. Lets suppose that the data in the GEN lump looks like this
GEN(93)=172 the ascii-rotate offset GEN(94)=23 the number of bits -1. 23+1 = 24, thats the total number of bits in the password There are 8 bits in each char of the password, so 24 divided by 8 is 3. That means this password will end up being 3 chars long. BIT OFFSETS/SCATTERTABLE GEN(199)=8 [0001 0000 0000 0000] dummy number. Never 0 or 255 GEN(200)=4 [0010 0000 0000 0000] Start here. bit 4 in the table is a 0 GEN(201)=3 [1100 0000 0000 0000] bit 3 in the table is a 1, now we have 01 GEN(202)=18[0100 1000 0000 0000] bit 18 is 1: 011 GEN(203)=18[0100 1000 0000 0000] bit 18 is 1: 0111 GEN(204)=54[0110 1100 0000 0000] bit 58 is 0: 01110 GEN(205)=33[1000 0010 0000 0000] bit 33 is 1: 011101 GEN(206)=85[1010 1010 0000 0000] bit 85 is 1: 0111011 GEN(207)=82[0100 1010 0000 0000] bit 82 is 1: 01110111
So we end up with 01110111 which is char 238, (a wierd ascii math symbol) Now we subtract the value of GEN(93) and we get 66, which is the ascii code for upper case B, so we know the first letter of the password is B.
Ah, what fun. what a mess. what a waste. *sigh*
PW1[edit]
We didn't bother to document this here, but it's yet another scheme, very similar to PW3! If you care, you can read the read_PW1_password() function in the source. It stores the password in gen(5) upwards, and in gen(genPW1Offset) and gen(genPW1Length) (gen(98) and gen(99).
See Also[edit]
- Garbage in GEN, an investigation of junk in high areas of GEN in older games
ARCHINYM.LMP
. ATTACK.BIN
. BINSIZE.BIN
. BROWSE.TXT
. DEFPAL#.BIN
. DEFPASS.BIN
. distrib.reld
. FIXBITS.BIN
. general.reld
. heroes.reld
. heroform.reld
. LOOKUP1.BIN
. MENUITEM.BIN
. MENUS.BIN
. PALETTES.BIN
. PLOTSCR.LST
. SFXDATA.BIN
. SLICELOOKUP.TXT
. slicetree_#_#.reld
. SONGDATA.BIN
. UICOLORS.BIN
Map Format :
E
. D
. L
. N
. P
. T
. Z
BAM
. DOR
. DOX
. DT0
. DT1
. DT6
. EFS
. FNT
. FOR
. GEN
. HSP
. HSZ
. ITM
. MAP
. MAS
. MN
. MXS
. PAL
. PT0 ... PT8
. RGFX
. SAY
. SHO
. SNG
. STF
. STT
. TAP
. TIL
. TMN
. VEH