Files

91 lines
2.2 KiB
Go
Raw Permalink Normal View History

2026-05-23 12:55:48 +08:00
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()
}