diff --git a/algo/ximalaya/x2m_crypto.go b/algo/ximalaya/x2m_crypto.go new file mode 100644 index 0000000..602a75e --- /dev/null +++ b/algo/ximalaya/x2m_crypto.go @@ -0,0 +1,34 @@ +package ximalaya + +import ( + _ "embed" + "encoding/binary" +) + +const x2mHeaderSize = 1024 + +var x2mKey = [...]byte{'x', 'm', 'l', 'y'} +var x2mScrambleTable = [x2mHeaderSize]uint16{} + +//go:embed x2m_scramble_table.bin +var x2mScrambleTableBytes []byte + +func init() { + if len(x2mScrambleTableBytes) != 2*x2mHeaderSize { + panic("invalid x3m scramble table") + } + for i := range x3mScrambleTable { + x3mScrambleTable[i] = binary.LittleEndian.Uint16(x2mScrambleTableBytes[i*2:]) + } +} + +// decryptX2MHeader decrypts the header of ximalaya .x2m file. +// make sure input src is 1024(x2mHeaderSize) bytes long. +func decryptX2MHeader(src []byte) []byte { + dst := make([]byte, len(src)) + for dstIdx := range src { + srcIdx := x2mScrambleTable[dstIdx] + dst[dstIdx] = src[srcIdx] ^ x2mKey[dstIdx%len(x2mKey)] + } + return dst +} diff --git a/algo/ximalaya/x2m_scramble_table.bin b/algo/ximalaya/x2m_scramble_table.bin new file mode 100644 index 0000000..ab586dd Binary files /dev/null and b/algo/ximalaya/x2m_scramble_table.bin differ diff --git a/algo/ximalaya/x3m_crypto.go b/algo/ximalaya/x3m_crypto.go new file mode 100644 index 0000000..b147944 --- /dev/null +++ b/algo/ximalaya/x3m_crypto.go @@ -0,0 +1,40 @@ +package ximalaya + +import ( + _ "embed" + "encoding/binary" +) + +var x3mKey = [...]byte{ + '3', '9', '8', '9', 'd', '1', '1', '1', + 'a', 'a', 'd', '5', '6', '1', '3', '9', + '4', '0', 'f', '4', 'f', 'c', '4', '4', + 'b', '6', '3', '9', 'b', '2', '9', '2', +} + +const x3mHeaderSize = 1024 + +var x3mScrambleTable = [x3mHeaderSize]uint16{} + +//go:embed x3m_scramble_table.bin +var x3mScrambleTableBytes []byte + +func init() { + if len(x3mScrambleTableBytes) != 2*x3mHeaderSize { + panic("invalid x3m scramble table") + } + for i := range x3mScrambleTable { + x3mScrambleTable[i] = binary.LittleEndian.Uint16(x3mScrambleTableBytes[i*2:]) + } +} + +// decryptX3MHeader decrypts the header of ximalaya .x3m file. +// make sure input src is 1024 (x3mHeaderSize) bytes long. +func decryptX3MHeader(src []byte) []byte { + dst := make([]byte, len(src)) + for dstIdx := range src { + srcIdx := x3mScrambleTable[dstIdx] + dst[dstIdx] = src[srcIdx] ^ x3mKey[dstIdx%len(x3mKey)] + } + return dst +} diff --git a/algo/ximalaya/x3m_scramble_table.bin b/algo/ximalaya/x3m_scramble_table.bin new file mode 100644 index 0000000..b43f50e Binary files /dev/null and b/algo/ximalaya/x3m_scramble_table.bin differ diff --git a/algo/ximalaya/ximalaya.go b/algo/ximalaya/ximalaya.go new file mode 100644 index 0000000..cbe5457 --- /dev/null +++ b/algo/ximalaya/ximalaya.go @@ -0,0 +1,56 @@ +package ximalaya + +import ( + "bytes" + "fmt" + "io" + + "unlock-music.dev/cli/algo/common" +) + +type Decoder struct { + rd io.ReadSeeker + offset int + + audio io.Reader +} + +func NewDecoder(rd io.ReadSeeker) common.Decoder { + return &Decoder{rd: rd} +} + +func (d *Decoder) Validate() error { + encryptedHeader := make([]byte, x2mHeaderSize) + if _, err := io.ReadFull(d.rd, encryptedHeader); err != nil { + return fmt.Errorf("ximalaya read header: %w", err) + } + + { // try to decode with x2m + header := decryptX2MHeader(encryptedHeader) + if _, ok := common.SniffAll(header); ok { + d.audio = io.MultiReader(bytes.NewReader(header), d.rd) + return nil + } + } + + { // try to decode with x3m + // not read file again, since x2m and x3m have the same header size + header := decryptX3MHeader(encryptedHeader) + if _, ok := common.SniffAll(header); ok { + d.audio = io.MultiReader(bytes.NewReader(header), d.rd) + return nil + } + } + + return fmt.Errorf("ximalaya: unknown format") +} + +func (d *Decoder) Read(p []byte) (n int, err error) { + return d.audio.Read(p) +} + +func init() { + common.RegisterDecoder("x2m", false, NewDecoder) + common.RegisterDecoder("x3m", false, NewDecoder) + common.RegisterDecoder("xm", false, NewDecoder) +} diff --git a/cmd/um/main.go b/cmd/um/main.go index 63765b4..d6d9adb 100644 --- a/cmd/um/main.go +++ b/cmd/um/main.go @@ -23,6 +23,7 @@ import ( _ "unlock-music.dev/cli/algo/qmc" _ "unlock-music.dev/cli/algo/tm" _ "unlock-music.dev/cli/algo/xiami" + _ "unlock-music.dev/cli/algo/ximalaya" "unlock-music.dev/cli/internal/logging" )