// Extract extracts CEL and CL2 archives. func Extract(archiveName string) (err error) { imageCount, found := imgconf.GetImageCount(archiveName) if !found { return fmt.Errorf("no archived images in %q.", archiveName) } archivePath, err := mpq.GetPath(archiveName) if err != nil { return err } fr, err := os.Open(archivePath) if err != nil { return err } defer fr.Close() fws, err := createOutputImages(archivePath, imageCount) if err != nil { return err } defer closeFiles(fws) ext := path.Ext(archiveName) switch ext { case ".cel": err = ExtractCel(fr, fws) case ".cl2": err = ExtractCl2(fr, fws) default: return fmt.Errorf("imgarchive.Extract: unknown extension: %q.", ext) } if err != nil { return fmt.Errorf("imgarchive.Extract: error while extracting %q: %s.", archiveName, err) } return nil }
// Parse parses a given TIL file and returns a slice of squares, based on the // TIL format described above. func Parse(tilName string) (squares []Square, err error) { tilPath, err := mpq.GetPath(tilName) if err != nil { return nil, err } fr, err := os.Open(tilPath) if err != nil { return nil, err } defer fr.Close() for { var x [4]uint16 err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { if err == io.EOF { break } return nil, err } square := Square{ PillarNumTop: int(x[0]), PillarNumRight: int(x[1]), PillarNumLeft: int(x[2]), PillarNumBottom: int(x[3]), } squares = append(squares, square) } return squares, nil }
// Parse parses a given SOL file and returns a slice of solids, based on the // SOL format described above. func Parse(solName string) (solids []Solid, err error) { solPath, err := mpq.GetPath(solName) if err != nil { return nil, err } fr, err := os.Open(solPath) if err != nil { return nil, err } defer fr.Close() var x uint8 for { err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { if err == io.EOF { break } return nil, err } var solid Solid if x&0x01 != 0 { solid.Sol0x01 = true } if x&0x02 != 0 { solid.Sol0x02 = true } if x&0x04 != 0 { solid.Sol0x04 = true } if x&0x08 != 0 { solid.Sol0x08 = true } if x&0x10 != 0 { solid.Sol0x10 = true } if x&0x20 != 0 { solid.Sol0x20 = true } if x&0x40 != 0 { solid.Sol0x40 = true } if x&0x80 != 0 { solid.Sol0x80 = true } solids = append(solids, solid) } return solids, nil }
// GetFrames returns a slice of frames, whose content has been retrieved based // on the CEL format described above. // // Note: The absolute path of celName is resolved using mpq.GetPath. func GetFrames(celName string) (frames [][]byte, err error) { // Open CEL file. celPath, err := mpq.GetPath(celName) if err != nil { return nil, err } f, err := os.Open(celPath) if err != nil { return nil, err } defer f.Close() // Read frame count. var frameCount uint32 err = binary.Read(f, binary.LittleEndian, &frameCount) if err != nil { return nil, fmt.Errorf("cel.GetFrames: unable to read frame count for %q: %v", celName, err) } // Read frame offsets. frameOffsets := make([]uint32, frameCount+1) err = binary.Read(f, binary.LittleEndian, frameOffsets) if err != nil { return nil, fmt.Errorf("cel.GetFrames: unable to read frame offsets for %q: %v", celName, err) } // Read frame contents. frames = make([][]byte, frameCount) for frameNum := range frames { // Ignore frame header. headerSize := imgconf.GetHeaderSize(celName) frameStart := int64(frameOffsets[frameNum]) + int64(headerSize) // Read frame content. frameEnd := int64(frameOffsets[frameNum+1]) frameSize := frameEnd - frameStart frame := make([]byte, frameSize) _, err = f.ReadAt(frame, frameStart) if err != nil { return nil, fmt.Errorf("cel.GetFrames: unable to read frame content for %q: %v", celName, err) } frames[frameNum] = frame } return frames, nil }
// Parse parses a given MIN file and returns a slice of pillars, based on the // MIN format described above. func Parse(minName string) (pillars []Pillar, err error) { minPath, err := mpq.GetPath(minName) if err != nil { return nil, err } fr, err := os.Open(minPath) if err != nil { return nil, err } defer fr.Close() var blockCount int switch minName { case "l1.min", "l2.min", "l3.min": blockCount = 10 case "l4.min", "town.min": blockCount = 16 } tmp := make([]uint16, blockCount) for { err = binary.Read(fr, binary.LittleEndian, &tmp) if err != nil { if err == io.EOF { break } return nil, err } pillar := Pillar{} pillar.Blocks = make([]Block, blockCount) for i := 0; i < blockCount; i++ { frameNumPlus1 := int(tmp[i] & 0x0FFF) if frameNumPlus1 != 0 { pillar.Blocks[i].IsValid = true pillar.Blocks[i].FrameNum = frameNumPlus1 - 1 } pillar.Blocks[i].Type = int(tmp[i]&0x7000) >> 12 } pillars = append(pillars, pillar) } return pillars, nil }
// Parse parses a given DUN file and stores each pillarNum at a coordinate in // the dungeon, based on the DUN format described above. // // Below is a description of how the squares are positioned on the dungeon map: // 1) Start at the coordinates colStart, rowStart. // 2) Place a square. // - Each square is two cols in width and two rows in height. // 3) Increment col with two. // 4) goto 2) dunQWidth number of times. // 5) Increment row with two. // 6) goto 2) dunQHeight number of times. // // ref: GetPillarRect (illustration of map coordinate system) // // Any additional cell data is stored afterwards using row major. func (dungeon *Dungeon) Parse(dunName string) (err error) { dunPath, err := mpq.GetPath(dunName) if err != nil { return err } fr, err := os.Open(dunPath) if err != nil { return err } defer fr.Close() var tmp [2]uint16 err = binary.Read(fr, binary.LittleEndian, &tmp) if err != nil { return err } dunQWidth := int(tmp[0]) dunQHeight := int(tmp[1]) colStart, err := dunconf.GetColStart(dunName) if err != nil { return err } rowStart, err := dunconf.GetRowStart(dunName) if err != nil { return err } nameWithoutExt, err := GetLevelName(dunName) if err != nil { return err } // squareNumsPlus1. squares, err := til.Parse(nameWithoutExt + ".til") if err != nil { return err } row := rowStart for i := 0; i < dunQHeight; i++ { col := colStart for j := 0; j < dunQWidth; j++ { var x uint16 err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { return err } squareNumPlus1 := int(x) if squareNumPlus1 != 0 { square := squares[squareNumPlus1-1] dungeon[col][row]["pillarNum"] = square.PillarNumTop dungeon[col+1][row]["pillarNum"] = square.PillarNumRight dungeon[col][row+1]["pillarNum"] = square.PillarNumLeft dungeon[col+1][row+1]["pillarNum"] = square.PillarNumBottom } col += 2 } row += 2 } dunWidth := 2 * dunQWidth dunHeight := 2 * dunQHeight // TODO: Figure out what these values are used for. Items? row = rowStart for i := 0; i < dunHeight; i++ { col := colStart for j := 0; j < dunWidth; j++ { var x uint16 err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { // Some DUN files only contain the pillar IDs. if err == io.EOF && i == 0 && j == 0 { return nil } return err } dungeon[col][row]["unknown"] = int(x) col++ } row++ } // dunMonsterIDs. row = rowStart for i := 0; i < dunHeight; i++ { col := colStart for j := 0; j < dunWidth; j++ { var x uint16 err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { if err == io.EOF && i == 0 && j == 0 { return nil } return err } // TODO: Lookup monster idx from dunMonsterID. // ref: 4B6C98 dungeon[col][row]["dunMonsterID"] = int(x) col++ } row++ } // dunObjectIDs. row = rowStart for i := 0; i < dunHeight; i++ { col := colStart for j := 0; j < dunWidth; j++ { var x uint16 err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { if err == io.EOF && i == 0 && j == 0 { return nil } return err } // TODO: Lookup object idx from dunObjectID. // ref: 4AAD28 dungeon[col][row]["dunObjectID"] = int(x) col++ } row++ } // transparencies. row = rowStart for i := 0; i < dunHeight; i++ { col := colStart for j := 0; j < dunWidth; j++ { var x uint16 err = binary.Read(fr, binary.LittleEndian, &x) if err != nil { if err == io.EOF && i == 0 && j == 0 { return nil } return err } dungeon[col][row]["transparency"] = int(x) col++ } row++ } return nil }