package main

import (
	"bytes"
	"crypto/aes"
	"encoding/binary"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os/user"
	"path/filepath"
)

/*
0 maybe device ID, not needed to work
1
2 lic name string
3 lic Type "0"
4 lic time "0"
5
*/

var keyFile string
var deviceId string

func initCmdline() {
	flag.StringVar(&keyFile, "k", "Key.data", "key file")
	flag.StringVar(&deviceId, "id", "HDO4XXXXXX", "devide Id")
	flag.Parse()
}

func Expand(path string) string {
	if len(path) == 0 || path[0] != '~' {
		return path
	}
	usr, err := user.Current()
	if err != nil {
		return path
	}
	return filepath.Join(usr.HomeDir, path[1:])
}

const delta = 0x9E3779B9

func mx(sum uint32, y uint32, z uint32, p uint32, e uint32, k []uint32) uint32 {
	return ((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum ^ y) + (k[p&3^e] ^ z))
}

func fixk(k []uint32) []uint32 {
	if len(k) < 4 {
		key := make([]uint32, 4)
		copy(key, k)
		return key
	}
	return k
}

func XXTeaEncrypt(v []uint32, k []uint32) []uint32 {
	length := uint32(len(v))
	n := length - 1
	k = fixk(k)
	var y, z, sum, e, p, q uint32
	z = v[n]
	sum = 0
	for q = 6 + 52/length; q > 0; q-- {
		sum += delta
		e = sum >> 2 & 3
		for p = 0; p < n; p++ {
			y = v[p+1]
			v[p] += mx(sum, y, z, p, e, k)
			z = v[p]
		}
		y = v[0]
		v[n] += mx(sum, y, z, p, e, k)
		z = v[n]
	}
	return v
}

func XXTeaDecrypt(v []uint32, k []uint32) []uint32 {
	length := uint32(len(v))
	n := length - 1
	k = fixk(k)
	var y, z, sum, e, p, q uint32
	y = v[0]
	q = 6 + 52/length
	for sum = q * delta; sum != 0; sum -= delta {
		e = sum >> 2 & 3
		for p = n; p > 0; p-- {
			z = v[p-1]
			v[p] -= mx(sum, y, z, p, e, k)
			y = v[p]
		}
		z = v[n]
		v[0] -= mx(sum, y, z, p, e, k)
		y = v[0]
	}
	return v
}

func HexString(data []byte) string {
	var buf bytes.Buffer
	for i := 0; i < len(data)-1; i++ {
		buf.WriteString(fmt.Sprintf("%02X ", data[i]))
	}
	buf.WriteString(fmt.Sprintf("%02X", data[len(data)-1]))
	return buf.String()
}

func HexStringS(data []byte, sep string) string {
	var buf bytes.Buffer
	for i := 0; i < len(data)-1; i++ {
		buf.WriteString(fmt.Sprintf("%02X%s", data[i], sep))
	}
	buf.WriteString(fmt.Sprintf("%02X", data[len(data)-1]))
	return buf.String()
}

func HexStringLsb(data []byte, sep string) string {
	var buf bytes.Buffer
	for i := 0; i < len(data)-1; i++ {
		buf.WriteString(fmt.Sprintf("%02x%s", (data[i]<<4)|(data[i]>>4), sep))
	}
	t := data[len(data)-1]
	buf.WriteString(fmt.Sprintf("%02x", (t<<4)|(t>>4)))
	return buf.String()
}

func decodeDefaultXXTEA(data []byte) []byte {
	k := []uint32{0x03920001, 0x08410841, 0x18C32104, 0x318639C7}
	N := len(data)
	u32 := make([]uint32, N>>2)
	for i, _ := range u32 {
		u32[i] = binary.LittleEndian.Uint32(data[i*4:])
	}
	u32d := XXTeaDecrypt(u32, k)
	res := make([]uint8, N)
	for i, v := range u32d {
		binary.LittleEndian.PutUint32(res[i*4:], v)
	}
	return res
}

func EncryptLicenseString(key []uint8, str string) (string, error) {
	block, err := aes.NewCipher(key)
	if nil != err {
		return "", err
	}
	x := make([]uint8, 48)
	copy(x, []uint8(str))
	block.Encrypt(x, x)
	block.Encrypt(x[0x10:], x[0x10:])
	block.Encrypt(x[0x20:], x[0x20:])
	return HexStringLsb(x, ""), nil
}

func MakeLicense(key []uint8, deviceId, feature string) string {
	s := fmt.Sprintf("%s#0#%s#4#0#0", deviceId, feature)
	res, err := EncryptLicenseString(key, s)
	if nil != err {
		return ""
	}
	return fmt.Sprintf("HDO4000-%s@%s", feature, res)
}

func LoadKeys() ([]uint8, error) {
	data, err := ioutil.ReadFile(Expand(keyFile))
	if nil != err {
		return nil, err
	}
	dd := decodeDefaultXXTEA(data)
	i := bytes.Index(dd, []uint8(";"))
	if -1 == i {
		return nil, errors.New("key format error")
	}
	fmt.Printf("Key: %v\n", string(dd))
	return dd[i+1:], nil

}

// BND
// MSO
// COMP
// EMBD
// AUTO
// FLEX
// AUDIO
// AERO
// ARINC
// DG
// JITTER
// EYE
// RTSA
// CM_USB
// CM_ENET
// CM_IPI
// CM_HDMI
// PWR
// UPA
// RLU
// BODE
// BW7T10
// BW7T20
// BW100T20
// BW2T4
// BW2T8
// BW4T8
// COUNT
// UNKNOWN

var options []string = []string{"EMBD", "AUTO", "BW2T8", "COMP", "AERO", "FLEX", "AUDIO", "RLU", "UPA"}

func main() {
	initCmdline()
	k, err := LoadKeys()
	if nil != err {
		fmt.Printf("error %v\n", err)
		return
	}
	fmt.Printf("Generating options for %s\n", deviceId)
	for _, o := range options {
		lic := MakeLicense(k[0:32], deviceId, o)
		fmt.Printf(":SYST:OPT:INST %v\n", lic)
	}

}
