init: dec-music 项目初始化

This commit is contained in:
2026-05-23 12:55:48 +08:00
commit 21045d0aad
50 changed files with 3662 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
package kwm
import (
"bytes"
"errors"
"fmt"
"io"
"strconv"
"strings"
"unicode"
"jsuse.com/dev/dec-music/decrypt"
)
const magicHeader1 = "yeelion-kuwo-tme"
const magicHeader2 = "yeelion-kuwo\x00\x00\x00\x00"
const keyPreDefined = "MoOtOiTvINGwd2E6n0E1i7L5t2IoOoNk"
type decoder struct {
rd io.ReadSeeker
cipher decrypt.StreamDecoder
offset int
outputExt string
bitrate int
}
func (d *decoder) GetAudioExt() string {
return "." + d.outputExt
}
func newDecoder(p *decrypt.DecoderParams) decrypt.Decoder {
return &decoder{rd: p.Reader}
}
// Validate checks if the file is a valid Kuwo .kw file.
// rd will be seeked to the beginning of the encrypted audio.
func (d *decoder) Validate() error {
header := make([]byte, 0x400) // kwm header is fixed to 1024 bytes
_, err := io.ReadFull(d.rd, header)
if err != nil {
return fmt.Errorf("kwm read header: %w", err)
}
// check magic header, 0x00 - 0x0F
magicHeader := header[:0x10]
if !bytes.Equal([]byte(magicHeader1), magicHeader) &&
!bytes.Equal([]byte(magicHeader2), magicHeader) {
return errors.New("kwm magic header not matched")
}
d.cipher = newKwmCipher(header[0x18:0x20]) // Crypto Key, 0x18 - 0x1F
d.bitrate, d.outputExt = parseBitrateAndType(header[0x30:0x38]) // Bitrate & File Extension, 0x30 - 0x38
return nil
}
func parseBitrateAndType(header []byte) (int, string) {
tmp := strings.TrimRight(string(header), "\x00")
sep := strings.IndexFunc(tmp, func(r rune) bool {
return !unicode.IsDigit(r)
})
bitrate, _ := strconv.Atoi(tmp[:sep]) // just ignore the error
outputExt := strings.ToLower(tmp[sep:])
return bitrate, outputExt
}
func (d *decoder) Read(b []byte) (int, error) {
n, err := d.rd.Read(b)
if n > 0 {
d.cipher.Decrypt(b[:n], d.offset)
d.offset += n
}
return n, err
}
func padOrTruncate(raw string, length int) string {
lenRaw := len(raw)
out := raw
if lenRaw == 0 {
out = string(make([]byte, length))
} else if lenRaw > length {
out = raw[:length]
} else if lenRaw < length {
_tmp := make([]byte, 32)
for i := 0; i < 32; i++ {
_tmp[i] = raw[i%lenRaw]
}
out = string(_tmp)
}
return out
}
func init() {
// Kuwo Mp3/Flac
decrypt.RegisterDecoder("kwm", false, newDecoder)
}
+31
View File
@@ -0,0 +1,31 @@
package kwm
import (
"encoding/binary"
"strconv"
)
type kwmCipher struct {
mask []byte
}
func newKwmCipher(key []byte) *kwmCipher {
return &kwmCipher{mask: generateMask(key)}
}
func generateMask(key []byte) []byte {
keyInt := binary.LittleEndian.Uint64(key)
keyStr := strconv.FormatUint(keyInt, 10)
keyStrTrim := padOrTruncate(keyStr, 32)
mask := make([]byte, 32)
for i := 0; i < 32; i++ {
mask[i] = keyPreDefined[i] ^ keyStrTrim[i]
}
return mask
}
func (c kwmCipher) Decrypt(buf []byte, offset int) {
for i := range buf {
buf[i] ^= c.mask[(offset+i)&0x1F] // equivalent: [i % 32]
}
}