91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
|
|
package qmc
|
||
|
|
|
||
|
|
import (
|
||
|
|
bytes "bytes"
|
||
|
|
"encoding/binary"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type musicExTagV1 struct {
|
||
|
|
songID uint32
|
||
|
|
unknown1 uint32
|
||
|
|
unknown2 uint32
|
||
|
|
mediaID string
|
||
|
|
mediaFileName string
|
||
|
|
unknown3 uint32
|
||
|
|
tagSize uint32
|
||
|
|
tagVersion uint32
|
||
|
|
tagMagic []byte
|
||
|
|
}
|
||
|
|
|
||
|
|
func newMusicExTag(f io.ReadSeeker) (*musicExTagV1, error) {
|
||
|
|
_, err := f.Seek(-16, io.SeekEnd)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("musicex seek error: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
buffer := make([]byte, 16)
|
||
|
|
bytesRead, err := f.Read(buffer)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("get musicex error: %w", err)
|
||
|
|
}
|
||
|
|
if bytesRead != 16 {
|
||
|
|
return nil, fmt.Errorf("MusicExV1: read %d bytes (expected %d)", bytesRead, 16)
|
||
|
|
}
|
||
|
|
|
||
|
|
tag := &musicExTagV1{
|
||
|
|
tagSize: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
|
||
|
|
tagVersion: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
|
||
|
|
tagMagic: buffer[0x08:0x10],
|
||
|
|
}
|
||
|
|
|
||
|
|
if !bytes.Equal(tag.tagMagic, []byte("musicex\x00")) {
|
||
|
|
return nil, errors.New("MusicEx magic mismatch")
|
||
|
|
}
|
||
|
|
if tag.tagVersion != 1 {
|
||
|
|
return nil, fmt.Errorf("unsupported musicex tag version: %d", tag.tagVersion)
|
||
|
|
}
|
||
|
|
|
||
|
|
if tag.tagSize < 0xC0 {
|
||
|
|
return nil, fmt.Errorf("unsupported musicex tag size: 0x%x", tag.tagSize)
|
||
|
|
}
|
||
|
|
|
||
|
|
buffer = make([]byte, tag.tagSize)
|
||
|
|
bytesRead, err = f.Read(buffer)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if uint32(bytesRead) != tag.tagSize {
|
||
|
|
return nil, fmt.Errorf("MusicExV1: read %d bytes (expected %d)", bytesRead, tag.tagSize)
|
||
|
|
}
|
||
|
|
|
||
|
|
tag.songID = binary.LittleEndian.Uint32(buffer[0x00:0x04])
|
||
|
|
tag.unknown1 = binary.LittleEndian.Uint32(buffer[0x04:0x08])
|
||
|
|
tag.unknown2 = binary.LittleEndian.Uint32(buffer[0x08:0x0C])
|
||
|
|
tag.mediaID = readUnicodeTagName(buffer[0x0C:], 30*2)
|
||
|
|
tag.mediaFileName = readUnicodeTagName(buffer[0x48:], 50*2)
|
||
|
|
tag.unknown3 = binary.LittleEndian.Uint32(buffer[0xAC:0xB0])
|
||
|
|
return tag, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// readUnicodeTagName reads a buffer to maxLen.
|
||
|
|
// reconstruct text by skipping alternate char (ascii chars encoded in UTF-16-LE),
|
||
|
|
// until finding a zero or reaching maxLen.
|
||
|
|
func readUnicodeTagName(buffer []byte, maxLen int) string {
|
||
|
|
builder := strings.Builder{}
|
||
|
|
|
||
|
|
for i := 0; i < maxLen; i += 2 {
|
||
|
|
chr := buffer[i]
|
||
|
|
if chr != 0 {
|
||
|
|
builder.WriteByte(chr)
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return builder.String()
|
||
|
|
}
|