qmc_footer_musicex.go 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package qmc
  2. import (
  3. bytes "bytes"
  4. "encoding/binary"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "strings"
  9. )
  10. type MusicExTagV1 struct {
  11. SongID uint32 // Song ID
  12. Unknown1 uint32 // unused & unknown
  13. Unknown2 uint32 // unused & unknown
  14. MediaID string // Media ID
  15. MediaFileName string // real file name
  16. Unknown3 uint32 // unused; uninitialized memory?
  17. // 16 byte at the end of tag.
  18. // TagSize should be respected when parsing.
  19. TagSize uint32 // 19.57: fixed value: 0xC0
  20. TagVersion uint32 // 19.57: fixed value: 0x01
  21. TagMagic []byte // fixed value "musicex\0" (8 bytes)
  22. }
  23. func NewMusicExTag(f io.ReadSeeker) (*MusicExTagV1, error) {
  24. _, err := f.Seek(-16, io.SeekEnd)
  25. if err != nil {
  26. return nil, fmt.Errorf("musicex seek error: %w", err)
  27. }
  28. buffer := make([]byte, 16)
  29. bytesRead, err := f.Read(buffer)
  30. if err != nil {
  31. return nil, fmt.Errorf("get musicex error: %w", err)
  32. }
  33. if bytesRead != 16 {
  34. return nil, fmt.Errorf("MusicExV1: read %d bytes (expected %d)", bytesRead, 16)
  35. }
  36. tag := &MusicExTagV1{
  37. TagSize: binary.LittleEndian.Uint32(buffer[0x00:0x04]),
  38. TagVersion: binary.LittleEndian.Uint32(buffer[0x04:0x08]),
  39. TagMagic: buffer[0x04:0x0C],
  40. }
  41. if !bytes.Equal(tag.TagMagic, []byte("musicex\x00")) {
  42. return nil, errors.New("MusicEx magic mismatch")
  43. }
  44. if tag.TagVersion != 1 {
  45. return nil, errors.New(fmt.Sprintf("unsupported musicex tag version. expecting 1, got %d", tag.TagVersion))
  46. }
  47. if tag.TagSize < 0xC0 {
  48. return nil, errors.New(fmt.Sprintf("unsupported musicex tag size. expecting at least 0xC0, got 0x%02x", tag.TagSize))
  49. }
  50. buffer = make([]byte, tag.TagSize)
  51. bytesRead, err = f.Read(buffer)
  52. if err != nil {
  53. return nil, err
  54. }
  55. if uint32(bytesRead) != tag.TagSize {
  56. return nil, fmt.Errorf("MusicExV1: read %d bytes (expected %d)", bytesRead, tag.TagSize)
  57. }
  58. tag.SongID = binary.LittleEndian.Uint32(buffer[0x00:0x04])
  59. tag.Unknown1 = binary.LittleEndian.Uint32(buffer[0x04:0x08])
  60. tag.Unknown2 = binary.LittleEndian.Uint32(buffer[0x08:0x0C])
  61. tag.MediaID = readUnicodeTagName(buffer[0x0C:], 30*2)
  62. tag.MediaFileName = readUnicodeTagName(buffer[0x48:], 50*2)
  63. tag.Unknown3 = binary.LittleEndian.Uint32(buffer[0xAC:0xB0])
  64. return tag, nil
  65. }
  66. // readUnicodeTagName reads a buffer to maxLen.
  67. // reconstruct text by skipping alternate char (ascii chars encoded in UTF-16-LE),
  68. // until finding a zero or reaching maxLen.
  69. func readUnicodeTagName(buffer []byte, maxLen int) string {
  70. builder := strings.Builder{}
  71. for i := 0; i < maxLen; i += 2 {
  72. chr := buffer[i]
  73. if chr != 0 {
  74. builder.WriteByte(chr)
  75. } else {
  76. break
  77. }
  78. }
  79. return builder.String()
  80. }