EotB – Filer

Jag har bestämt mig för att äntligen ta tag i ett projekt som intresserat mig sedan 1991. Det handlar om spelet Eye of the Beholder (EotB). Spelet är ett klassiskt rollspel från tiden innan 3D-kort som bygger på Dungeon & Dragons systemet.

Denna artikelserie kommer att handla om försöken att återskapa en gammal klassiker med originalljud och originalgrafik. Men varför vill man göra det? Jo, helt enkelt för att det är skoj att veta exakt hur ett spel är konstruerat. Ta t.ex. Doom som brukar portas till alla möjliga plattformar. Projektet kommer att ge oss kunskap om hur vi kan modifiera originalspelet men också hur vi kan göra moderna fan-versioner i t.ex. MonoGame.

Eye of the Beholder – Amiga

Eye of the Beholder – C64

PC- och Amiga-versionen är lika varandra. Ovan till höger ser du också en C64-version som påbörjades runt 2018 av Andreas Larsson. Det var faktiskt C64-versionen som inspirerade till denna serie. Det ska tilläggas att C64-versionen är ruskigt snygg för att vara C64, en maskin med 64kB minne totalt, 8 bitar och 16 färger!

Som du kan se av filmerna ovan så kan man vända sig 90° i taget. Det är alltså ingen 3D utan ren 2D-grafik. Spelsystemet bygger på Dungeons & Dragons med alla möjliga ”items” och möjlighet att ”levla up”.

Verktyg

För att kunna påbörja arbetet behöver vi utrustas oss med lämpliga verktyg:

Eftersom rättigheterna till grafik och ljud fortfarande ägs (troligtvis) så kommer vi inte att tillhandahålla någon sådan data. Det är dock inte så svårt att hitta dessa på nätet, se länk ovan.

Filstruktur

Strukturen är ganska enkel. När det begav sig 1991 så lagrades spelen på disketter. En typisk DOS-diskett rymde 720kB eller 1.44Mb. Till PC troligen en diskett men på Amiga som använde 880kB disketter så blev det två. Några filändelser kan vi nog gissa:

  • .EXE är körbara filer
  • .SAV är troligen sparfilen till spelet (save)
  • .FNT är troligen typsnitt
  • .PAK verkar vara data-filer

PAK filer

Med en snabb googling upptäcker vi att .pak är ett data-format som Westwood använt till flera av sina spel, t.ex. Eye of the Beholder I, Dune II, Lands of Lore I, Legend of Kyrandia, etc. Det finns också flera versioner varav version 1 endast användes av Eye of the Beholder I.

Källa: http://www.shikadi.net/moddingwiki/PAK_Format_(Westwood)

Kikar vi på dokumentationen så sägs det att PAK-filen är en samling av andra filer, ungefär som en zip-fil fast utan kompression. Filen börjar med följande:

  1. 4 bytes offset, en uint32 i little endian format
  2. null-terminerad sträng, variabel längd

Little endian betyder att de bytes som är minst signifikanta kommer först. Först anges egentligen hur stor ”headern” på filen är. Det är den första filen i pak-filens start-adress i filen, en så kallad offset. Sedan följer filnamnet som vanligtvis brukar följa gammal DOS-standard om max 8 tecken, sedan punkt följt av tre tecken.

”Headern” avslutas med 4 bytes som är storleken på själva pak-filen. För version 2 o 3 så är det värdet istället 0. Sedan finns en liten brasklapp:

Note that while version 1 PAK files normally have the end offset set to the file size, about half of the PAK files in Eye of the Beholder 1 have it set to complete garbage. The game doesn’t actually use the end offset in any way; it has a table of file sizes hardcoded in the exe file, and relies on that to extract the files correctly. This should be taken into account when dealing with version 1 PAK files

Men hur ser en pak-fil ut då egentligen? Vi startar HxD pch kikar på EOBDATA2.PAK.

De blå-markerade avsnitten är offset. De orange-markerade delarna är filnamnen på filerna i pak-filen. Den röda markeringen är för att visa null-termineringen på strängarna till filnamnen. ”null” är alltså värdet 00. Det gröna området är fil-data från filen DOOR.EGA.

Eftersom det är i ”little endian” så blir 20 00 00 00 istället 00 00 00 20 i ”big endian”. Värdet är egentligen detsamma men lagras på två olika vis. I bilden ovan har vi alltså en offset på 0x20 vilket är 32 decimalt. Det stämmer då med den gröna markeringen att filens data börjar på index 32.

Dags att skriva ett program som kan läsa detta format.

En avkodare till .pak

Vi behöver en data-klass för att fånga upp saker som filnamn, storlek, data, ect för varje fil i pakfilen.

FileInfo.cs
namespace PakExtract
{
    class FileInfo
    {
        public string FileName { get; set; }
        public int FileSize { get; set; }
        public int OffsetInPak { get; set; }
        public byte[] RawData { get; set; }
    }
}

Själva logiken för att koda av pak-filen blir enkel när vi förstår oss på formatet.

PakFile.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;

namespace PakExtract
{
    class PakFile
    {
        public ReadOnlyCollection<FileInfo> ReadOnlyList => _files.AsReadOnly();

        private List<FileInfo> _files;
        private byte[] _data;

        private PakFile(string fileName)
        {
            _files = new List<FileInfo>();
            _data = File.ReadAllBytes(fileName);

            // read header
            int offset = 0;
            int headerSize = _data[0] + (_data[1] << 8) +
                    (_data[2] << 16) + (_data[3] << 24);

            while (offset < headerSize-4)
            {
                var info = new FileInfo();
                info.OffsetInPak = _data[offset] + (_data[offset + 1] << 8) +
                    (_data[offset + 2] << 16) + (_data[offset + 3] << 24);
                offset += 4;

                var nameData = new List<byte>();
                while (_data[offset] != 0)
                    nameData.Add(_data[offset++]);

                info.FileName = Encoding.UTF8.GetString(nameData.ToArray());
                offset++;

                int endOffset = _data[offset] + (_data[offset + 1] << 8) +
                    (_data[offset + 2] << 16) + (_data[offset + 3] << 24);
                info.FileSize = endOffset - info.OffsetInPak;

                info.RawData = new byte[info.FileSize];
                Array.Copy(_data, info.OffsetInPak, info.RawData, 0, info.FileSize);

                _files.Add(info);
            }

        }

        public static PakFile FromFile(string fileName)
        {
            return new PakFile(fileName);
        }
    }
}

Avslutning

Programmet verkar kunna koda av pak-formatet nu.

Detta är en bra början. Nästa steg får nog bli att kunna koda av de gamla bildformaten .cmp och .ega. Hoppas du tyckte det var intressant.

Nästa artikel

EotB - Grafikformat

Vilket grafikformat användes och hur kan vi läsa det?

Scroll to top