A brief look at Der Langrisser data structures and offsets
By Derrick Sobodash
gopher://somniafabularum.com/

First published December 21, 2007
Last updated December 24, 2007

This work is hereby declared as part of the Public Domain.
The author reserves no rights.

-------------------------------------------------------------------------------

CONTENTS

   I. INTRODUCTION
  II. CHANGES
 III. CHARACTER BASE STATS
  IV. ITEM STATS
   V. CLASS STATS
  VI. SCENARIO SCRIPTS
 VII. FONT FORMAT
VIII. WINDOW COLORS
  IX. ENABLE THE BATTLE TEST
   A. APPENDIX I

-------------------------------------------------------------------------------

I. INTRODUCTION

I decided to write this to track my tweaking of Der Langrisser's internal 
structs. For years people asked about making a "hard mode" of Der Langrisser, 
so this seemed like a great way to help everyone out.

I first found the tables while using hacking notes a Chinese netizen "maxmat" 
posted to the Emumax gaming forum for Der Langrisser. He made a few patches 
to make early classes get super units, and I intended to use this to solve the 
problem of Elementals as non-hireable units.

Then I kept poking around, and based on his patch and a few others floating 
around, located the bulk of the data structures. I had one thing to help 
figure out what every entry is that maxmat did not: the scripts.

So here, I present the data structures as best I understand them, along with 
the arrays to fill in all the values so they make sense.

If you are interested, you can view these structs as dumped to spreadsheets 
at my Google Docs account:

http://spreadsheets.google.com/ccc?key=pi_0ICQyQm8QTcTx_5GQbSw&hl=en

You will need a Google account or Gmail account to get in.

Anyone who can help to fill in the last missing values, please do!

I am also looking for a table to control class changes, and possible to 
control the stats of NPC and Enemy units in each scenario. This could be in
the compressed scenario scription blocks which control dialogue and turns.

-------------------------------------------------------------------------------

II. CHANGES

2007.12.29 - Added font storage and mapping information.

2007.12.24 - Updated unit types table to match manual's affinity listing.
             Added location of pointers to scenario scripts.

-------------------------------------------------------------------------------

III. CHARACTER BASE STATS

These are the stats each playable character begins with before he/she/it 
starts gaining experience. Struct entries are each 16 bytes long. There are
18 entries which correspond to the 18 entries in the array COMMANDER_NAMES.

Start Offset: 0x016ce0

struct BASE_STATS
{
  int8 AT,
  int8 DF,
  int24 Spells,    /* Bit string where turns entries in magic array on/off */
  bool Summon,     /* Store as int8, 0=off/1=on */
  int8 UNKNOWN1,
  int8 Portrait,
  int8 Troops,     /* Number of troops hireable */
  int8 Unit 1,     /* From CLASS_NAMES array */
  int8 Unit 2,
  int8 Unit 3,
  int32 UNKNOWN2
}

Regarding the Spells entry. It works like this. If you had the big Endian 
value 0x800200, that is 100000000000001000000000 in binary. However, the bits 
of each byte are mirrored left to right. This should be interpreted to be: 
000000010100000000000000. Laying this over the SPELL_NAMES array, it will turn 
on the spells Heal 1 and Turn Undead.

-------------------------------------------------------------------------------

IV. ITEM STATS

Each item is able to have four properties assigned to it. The four can be 
mixed and matched from the array ITEM_PROPS. The following value is a signed 
8-bit integer to offset that property. In the case of Summon, it pulls the 
spell name from the array SUMMONS. The struct contains 36 entries which 
correspond to the array ITEM_NAMES. Each entry is 8 bytes.

Start Offset: 0x014fb1

struct ITEM_STATS
{
  int8 Property1,  /* From ITEM_PROPS */
  int8 Value1,
  int8 Property2,  /* From ITEM_PROPS */
  int8 Value2,
  int8 Property3,  /* From ITEM_PROPS */
  int8 Value3,
  int8 Property4,  /* From ITEM_PROPS */
  int8 Value4
}

-------------------------------------------------------------------------------

V. CLASS STATS

These are the statistics used to determine what base values a class has. In 
the case of a class change, certain stats will be added cumulatively to the 
character's base: for example, AT/DF/A/D/MP. You will notice certain stats are 
far higher in hireable units, because you cannot change to classes such as 
Lizard Man. There are 255 entries corresponding to the game's class index 
table stored in array CLASS_NAMES. Each entry is 26 bytes.

Start Offset: 0x036240

struct CLASS_STATS
{
  int8 0x0,
  bool Flying,     /* Determines true flying status. Battle screen y placement? */
  int8 0x0,
  uint8 Type,      /* from UNIT_TYPES */
  uint8 Tier,
  int8 AT,
  int8 DF,
  int8 MV,
  int8 Range,      /* Command range */
  int8 A,
  int8 D,
  uint8 MP,
  int8 MagicDef,
  int8 Troops,
  uint8 Cost,      /* Cost to hire in P. Multiply by 10 */
  int32 UNKNOWN,
  uint8 Unit1,     /* From CLASS_NAMES array */
  uint8 Unit2,
  uint8 Unit3,
  uint8 Spell1,    /* From SPELL_NAMES array */
  uint8 Spell2,
  uint8 Spell3,
  uint8 Spell4,
}

-------------------------------------------------------------------------------

VI. SCENARIO SCRIPTS

Der Langrisser contains 94 scripts used for scenario and event text.

Offset: 0x120000

Each pointer is a 24-bit little Endian integer. The script runs from pointer
to pointer, so script 0 ends when you hit the offset where script 1 begins.

The game uses a simplistic series of control codes as follows:

0x00 - End window
0x01 - Change text color, takes next byte as palette color
0x02 - Main character's name
0x03 - Variable (number or string)
0x04 - Word table entry (script 7), takes next byte as entry number
0x06 - Pause text rendering
0x07 - Clear window
0x08 - Line break
0x09 - Character name table entry (script 1), takes next byte as entry number
0xfc - Switch to font bank 1
0xfd - Switch to font bank 2
0xfe - Switch to font bank 3
0xff - Switch to font bank 4

-------------------------------------------------------------------------------

VII. FONT FORMAT

Der Langrisser uses a monochrome font with each character rendered on a 12x12
pixel tile. However, the way it compresses its bytes is quite unlike the way
most such tiles are stored.

As one would expect, three bytes, or 24 bits, are required to draw two lines
of any given glyph. If we have three bytes a, b and c, their bits will be
arranged in the following style:

a0 a1 a2 a3 a4 a5 a6 a7 b0 b1 b2 b3
c0 c1 c2 c3 c4 c5 c6 c7 b4 b5 b6 b7

This pattern is repeated so that 18 bytes are used to completely draw one
12x12 pixel character glyph. The glyphs are called by the script engine using
a combination of 0xfc, 0xfc, 0xfe or 0xff in conjunction with a secondary byte
greater than or equal to 0x10 and less than or equal to 0xef, allowing for
banks with a maximum 224 glyphs each.

-------------------------------------------------------------------------------

VIII. WINDOW COLORS

While not a data structure, some people may wish to experiment with these. 
These values will only work in a Japanese Der Langrisser ROM. In the English 
version, we hijacked these values to store them elsewhere because of the new 
color switch option.

The dialogue windows use what is called Color Add/Sub, not alpha blending. 
What it does is add or subtract a fixed amount of each color from each pixel 
within the field. Color Add will make things brighter, and Color Subtract will 
make them darker. You can't have it both ways.

Offset: 0x0042d4

Possible Values
{
  0x03 = Color Add
  0x83 = Color Subtract
}

The actual color additions and subtractions are stored a little further in.

Offset: 0x00437a

struct COLOR_TABLE
{
  int8 AllyColor1,
  int8 AllyColor2,
  int8 AllyColor3,
  int8 NPCColor1,
  int8 NPCColor2,
  int8 NPCColor3,
  int8 EnemyColor1,
  int8 EnemyColor2,
  int8 EnemyColor3
}

These color entries are SFC colors. For example, the default Ally window 
colors are 0x30 0x50 0x81, or binary 001 10000, 010 10000 and 100 00001. In 
the SFC Add/Sub, colors follow a pattern of bgriiiii. The first three bits are 
a toggle saying whether to modify Blue (100), Green (010) or Red (001). The 
last 5 bits are a value to offset by, giving you a possibility of 00000 to 
11111, or 32 levels.

The last value is the background color used in all non-dialogue windows. This 
is a 16-bit color.

Offset: 0x007ba5

The default value is the little Endian word 0x1800 (stored in the ROM as 00 
18), or 0001100000000000 in binary. The format for any SFC palette color is 
xbbbbbgggggrrrrr, where red, green and blue are 5 bits each. Using the above 
example, we have a value of rgb(0, 0, 6).

-------------------------------------------------------------------------------

IX. ENABLE THE BATTLE TEST

The Battle Test was first discovered in alamone's Der Langrisser script dumps 
in 1998, and for years people scratched their heads over how to add it. The 
Der Langrisser English patch was the first time people could see the battle 
menu, and here is how it happened.

Offset: 0x10e075

In the Japanese game, this value is 0x02. This is the number of options to 
show in the Media Test (from the load menu: Left, Right, Select A). All one 
needs to do is change this value to 0x03, and magically the Battle Test 
option will appear.

Be warned, the Japanese one is nowhere near as refined as the English one, 
and the majority of options in it appear as total nonsense with random 
letters tacked onto the end. If you want a clear list of what each unit will 
be, you are better off using the English patch.

byuu is the one who found this offset.

-------------------------------------------------------------------------------

A. APPENDIX I

static array UNIT_TYPES
{
  string "Soldier",
  string "Monk",
  string "Spearman",
  string "Cavalry",
  string "Dragoon",
  string "Flier",
  string "Bandit",
  string "Sailor",
  string "Gel",
  string "Demon",
  string "Monster",
  string "Barbarian",
  string "Magician",
  string "Ghost",
  string "Undead",
  string "Bowman",
  string "Ballista",
  string "Dragon"
}

static array ITEM_PROPS
{
  string "AT",
  string "DF",
  string "MV",
  string "Range",
  string "A",
  string "D",
  string "Magic Range",
  string "Magic Damage",
  string "Magic Defense",
  string "Summon"
}

static array COMMANDER_NAMES
{
  string "Erwin",
  string "Liana",
  string "Lana",
  string "Cherie",
  string "Hein",
  string "Scott",
  string "Keith",
  string "Aaron",
  string "Lester",
  string "Lana",           /* Dark Princess */
  string "Rohga",
  string "Sonya",
  string "Leon",
  string "Vargas",
  string "Imelda",
  string "Egbert",
  string "Esto",
  string "Osto"
};

static array ITEM_NAMES
{
  string "Not Equipped",
  string "Dagger",
  string "War Hammer",
  string "Long Sword",
  string "Magic Wand",
  string "Inferno Lance",
  string "Devil Axe",
  string "Dragon Slayer",
  string "Langrisser",
  string "Langrisser",     /* Unsealed */
  string "Iron Dumbbell",
  string "Masayan Sword",
  string "Orb",
  string "Holy Rod",
  string "Holy Rod",
  string "Dark Rod",
  string "Long Bow",
  string "Arbalest",
  string "Alhazard",
  string "Alhazard",       /* Unsealed */
  string "Odin's Buckler",
  string "Buckler",
  string "Shield",
  string "Chainmail",
  string "Platemail",
  string "Assault Suit",
  string "Cloak",
  string "Dragon Scale",
  string "Wizard's Robe",
  string "Amulet",
  string "Cross",
  string "Necklace",
  string "Swift Boots",
  string "Crown",
  string "Gleipnir",
  string "Rune Stone"
}

static array CLASS_NAMES
{
  string "        ",
  string "Fighter",        /* Imperial */
  string "Fighter",        /* Light */
  string "Fighter",
  string "Gladiator",      /* Imperial */
  string "Vampire",
  string "Knight",         /* Imperial */
  string "Knight",         /* Light */
  string "Pirate",         /* Light */
  string "Hawk Knight",    /* Imperial */
  string "Hawk Knight",    /* Light */
  string "Sister",
  string "Shaman",         /* Imperial */
  string "Warlock",        /* Imperial */
  string "Warlock",        /* Light */
  string "Werewolf",
  string "Gelgazer",
  string "Ghost",
  string "Scylla",
  string "Roc",
  string "Lord",           /* Imperial */
  string "Lord",           /* Light */
  string "Lord",
  string "Assassin",       /* Imperial */
  string "Assassin",       /* Light */
  string "Silver Knight",  /* Imperial */
  string "Silver Knight",  /* Light */
  string "Silver Knight",
  string "Captain",        /* Imperial */
  string "Captain",        /* Light */
  string "Hawk Lord",      /* Imperial */
  string "Hawk Lord",      /* Light */
  string "Cleric",
  string "Necromancer",    /* Imperial */
  string "Sorcerer",       /* Imperial */
  string "Sorcerer",       /* Light */
  string "Sorcerer",
  string "Paladin",
  string "Paladin",
  string "Kerberos",
  string "Dullahan",
  string "Lich",
  string "Serpent",
  string "Wyvern",
  string "High Lord",      /* Imperial */
  string "High Lord",      /* Light */
  string "High Lord",
  string "Swordsman",      /* Imperial */
  string "Swordsman",      /* Light */
  string "Highlander",     /* Imperial */
  string "Highlander",     /* Light */
  string "Highlander",
  string "Serpent Knight",
  string "Gladiator",
  string "Dragon Knight",  /* Imperial */
  string "Dragon Knight",  /* Light */
  string "Priest",
  string "Summoner",       /* Imperial */
  string "Mage",           /* Imperial */
  string "Mage",           /* Light */
  string "Mage",
  string "Saint",
  string "Saint",
  string "Unicorn Knight", /* Cherie */
  string "Minotaur",
  string "Living Armour",
  string "Succubus",
  string "Kraken",
  string "Phoenix",
  string "General",        /* Imperial */
  string "General",        /* Light */
  string "General",
  string "Sword Master",   /* Imperial */
  string "Sword Master",   /* Light */
  string "Knight Master",  /* Imperial */
  string "Knight Master",  /* Light */
  string "Knight Master",
  string "Serpent Lord",   /* Imperial */
  string "Pirate",
  string "Dragon Lord",    /* Chaos */
  string "Dragon Lord",    /* Light */
  string "High Priest",
  string "Zauberer",       /* Imperial */
  string "Zauberer",       /* Chaos */
  string "Archmage",       /* Imperial */
  string "Archmage",       /* Light */
  string "Archmage",
  string "Sage",
  string "Sage",
  string "Ranger",         /* Aaron */
  string "Ranger",         /* Rohga */
  string "Master Dino",
  string "Stone Golem",
  string "Vampire Lord",
  string "Jormungandr",
  string "Great Dragon",
  string "King",
  string "Emperor",        /* Bernhardt */
  string "Hero",
  string "Hero",
  string "Hero",
  string "Queen",          /* Imelda */
  string "Royal Guard",    /* Sonya */
  string "Royal Guard",    /* Leon */
  string "Royal Guard",    /* Scott */
  string "Royal Guard",    /* Erwin */
  string "Serpent Master", /* Light */
  string "Dragon Master",  /* Light */
  string "Agent",          /* Liana */
  string "Dark Master",    /* Boser */
  string "Dark Master",    /* Egbert */
  string "Wizard",         /* Imperial */
  string "Wizard",         /* Imperial */
  string "Wizard",         /* Light */
  string "Wizard",         /* Light */
  string "Princess",       /* Cherie */
  string "Sorcerer",       /* Imperial */
  string "High Master",    /* Aaron */
  string "High Master",    /* Rohga */
  string "Monk",
  string "Barbarian",
  string "Soldier",        /* Imperial */
  string "Soldier",        /* Light */
  string "Soldier",
  string "Berserker",
  string "Grenadier",      /* Imperial */
  string "Grenadier",      /* Light */
  string "Grenadier",
  string "Dark Guard",
  string "Lancer",
  string "Trooper",        /* Light */
  string "Trooper",
  string "Hell Hound",
  string "Royal Lancer",
  string "Dragoon",        /* Light */
  string "Dragoon",
  string "Bone Dino",
  string "Pike",           /* Imperial */
  string "Pike",           /* Light */
  string "Pike",
  string "Phalanx",        /* Imperial */
  string "Phalanx",        /* Light */
  string "Phalanx",
  string "Golem",
  string "Elf",            /* Imperial */
  string "Elf",            /* Light */
  string "Elf",
  string "Dark Elf",
  string "High Elf",       /* Imperial */
  string "High Elf",       /* Light */
  string "High Elf",
  string "Witch",
  string "Ballista",       /* Chaos */
  string "Ballista",       /* Imperial */
  string "Ballista",       /* Light */
  string "Ballista",
  string "Ghost",
  string "Spectre",
  string "Demon",
  string "Archdemon",
  string "Merman",         /* Light */
  string "Merman",
  string "Lizard Man",     /* Chaos */
  string "Lizard Man",     /* Imperial */
  string "Nixie",          /* Light */
  string "Nixie",
  string "Leviathan",      /* Chaos */
  string "Leviathan",      /* Imperial */
  string "Harpy",          /* Imperial */
  string "Harpy",
  string "Fairy",
  string "Bat",
  string "Griffin",        /* Imperial */
  string "Griffin",
  string "Angel",
  string "Gargoyle",
  string "Monk",           /* Light */
  string "Barbarian",      /* Imperial */
  string "Crusader",       /* Light */
  string "Crusader",
  string "Barbarian",      /* Imperial */
  string "Barbarian",
  string "Bandit",         /* Imperial */
  string "Bandit",
  string "Zombie",
  string "Skeleton",
  string "Wolfman",
  string "Ogre",
  string "Gel",
  string "Elemental",
  string "Civilian",
  string "Valkyrie",
  string "Freyja",
  string "White Dragon",
  string "Salamander",
  string "Iron Golem",
  string "Demon Lord",
  string "Sleipnir",
  string "Fenrir",
  string "Aniki",
  string "Bodybuilder",
  string "Knight Master",  /* Leon */
  string "Emperor",        /* Rohga */
  string "Zauberer",       /* Egbert */
  string "Assassin",
  string "Dark Princess",  /* Lana */
  string "Wizard",         /* Lana */
  string "Serpent Knight", /* Lester */
  string "Dragon Knight",  /* Keith */
  string "Dragon Lord",    /* Keith */
  string "Noble",          /* Eliza */
  string "Dragon Knight",  /* Cherie */
  string "Royal Guard",    /* Imelda */
  string "Archmage",       /* Jessica */
  string "High Lord",      /* Aaron */
  string "Sword Master",   /* Aaron */
  string "Knight",         /* Imperial */
  string "Serpent Lord",   /* Lester */
  string "Knight Master",  /* Scott */
  string "Assassin",       /* Imperial */
  string "Ranger",         /* Rohga */
  string "High Master",    /* Rohga */
  string "Archmage",       /* Sonya */
  string "Wizard",         /* Sonya */
  string "High Priest",    /* Liana */
  string "Chaos",
  string "Bishop",
  string "Lushiris",
  string "Sorcerer",       /* Imperial */
  string "Silver Knight",  /* Imperial */
  string "Necromancer",    /* Imperial */
  string "Saint",          /* Imperial */
  string "Mage",           /* Imperial */
  string "Swordsman",      /* Imperial */
  string "Highlander",     /* Imperial */
  string "Saint",          /* Imperial */
  string "Summoner",       /* Imperial */
  string "Swordsman",      /* Light */
  string "Serpent Knight", /* Light */
  string "Priest",
  string "Hawk Knight",
  string "Dragon Lord",    /* Chaos */
  string "Sage",           /* Chaos */
  string "Sage",           /* Chaos */
  string "Archmage",       /* Imperial */
  string "General",        /* Imperial */
  string "Sword Master",   /* Chaos */
  string "Serpent Lord",   /* Imperial */
  string "Sword Master",   /* Light */
  string "High Priest",
  string "Sage",           /* Cherie */
  string "General",        /* Vargas */
  string "Knight Master",  /* Imperial */
  string "Pirate",         /* Imperial */
  string "Knight"          /* Light */
}

static array SPELL_NAMES
{
  string "Magic Arrow",
  string "Blast",
  string "Thunder",
  string "Fireball",
  string "Meteor",
  string "Blizzard",
  string "Tornado",
  string "Turn Undead",
  string "Earthquake",
  string "Heal 1",
  string "Heal 2",
  string "Force Heal 1",
  string "Force Heal 2",
  string "Sleep",
  string "Mute",
  string "Protection 1",
  string "Protection 2",
  string "Attack 1",
  string "Attack 2",
  string "Zone",
  string "Teleport",
  string "Resist",
  string "Charm",
  string "Quick",
  string "Again",
  string "Decrease",
  string "Valkyrie",
  string "Freyja",
  string "White Dragon",
  string "Salamander",
  string "Iron Golem",
  string "Demon Lord"
}

static array SUMMON_NAMES
{
  string "Valkyrie",
  string "Freyja",
  string "White Dragon",
  string "Salamander",
  string "Iron Golem",
  string "Demon Lord",
  string "Sleipnir",
  string "Fenrir",
  string "Aniki",
  string "Aniki"           /* Enemy? */
}

-------------------------------------------------------------------------------