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() }