feat: Themes added, 4 themes available (3 dark, 1 light)
This commit is contained in:
+83
-13
@@ -54,6 +54,65 @@ func ctrlPacket(typ uint16, seq uint16, sentid, rcvdid uint32) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// passcodeSeq is Icom's fixed obfuscation table for the login username/password
|
||||
// (used by RS-BA1). BEST-EFFORT public reconstruction — the values that matter
|
||||
// for a given credential are sequence[char+index]; if the radio rejects auth,
|
||||
// compare the "scrambled" bytes this tool prints against a real login capture to
|
||||
// correct the needed entries.
|
||||
var passcodeSeq = [256]byte{
|
||||
0x47, 0x5d, 0x4c, 0x42, 0x66, 0x20, 0x23, 0x46, 0x4e, 0x57, 0x45, 0x3d, 0x67, 0x76, 0x60, 0x41,
|
||||
0x62, 0x39, 0x59, 0x2d, 0x68, 0x7e, 0x20, 0x77, 0x5f, 0x51, 0x3e, 0x70, 0x4d, 0x1f, 0x74, 0x38,
|
||||
0x2c, 0x4b, 0x1e, 0x54, 0x30, 0x71, 0x2b, 0x2a, 0x66, 0x27, 0x2e, 0x58, 0x24, 0x21, 0x2f, 0x50,
|
||||
0x1b, 0x73, 0x69, 0x36, 0x1d, 0x4f, 0x1c, 0x51, 0x2e, 0x1e, 0x45, 0x2e, 0x22, 0x50, 0x64, 0x66,
|
||||
0x24, 0x36, 0x0c, 0x7d, 0x50, 0x25, 0x7c, 0x3f, 0x2d, 0x35, 0x71, 0x6a, 0x0e, 0x41, 0x2a, 0x67,
|
||||
0x7c, 0x64, 0x77, 0x67, 0x6d, 0x5b, 0x3d, 0x5b, 0x2b, 0x67, 0x6c, 0x39, 0x35, 0x76, 0x3b, 0x2f,
|
||||
0x2f, 0x6d, 0x59, 0x6e, 0x59, 0x77, 0x3b, 0x24, 0x74, 0x7c, 0x6b, 0x37, 0x54, 0x5c, 0x4d, 0x1f,
|
||||
0x27, 0x69, 0x5b, 0x2e, 0x28, 0x35, 0x77, 0x74, 0x35, 0x1f, 0x6a, 0x2a, 0x28, 0x30, 0x25, 0x20,
|
||||
}
|
||||
|
||||
// passcode scrambles s (username or password) via the Icom sequence table.
|
||||
func passcode(s string) []byte {
|
||||
out := make([]byte, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
p := int(s[i]) + i
|
||||
if p > 0x7f {
|
||||
p = ((p - 0x7f) % 0x33) - 1
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
}
|
||||
out[i] = passcodeSeq[p&0xff]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// buildLogin builds the 0x80-byte login packet: control header + username/
|
||||
// password (scrambled) at 0x40/0x50 and the app name at 0x60. The middle fields
|
||||
// (payload size, request type, inner seq, token request) are a best-effort
|
||||
// reconstruction and may need adjustment against a capture.
|
||||
func buildLogin(seq uint16, sentid, rcvdid uint32, innerSeq, tokRequest uint16, user, pass, name string) []byte {
|
||||
b := make([]byte, 0x80)
|
||||
binary.LittleEndian.PutUint32(b[0:], 0x80) // len
|
||||
// type (b[4:6]) = 0x00
|
||||
binary.LittleEndian.PutUint16(b[6:], seq)
|
||||
binary.LittleEndian.PutUint32(b[8:], sentid)
|
||||
binary.LittleEndian.PutUint32(b[12:], rcvdid)
|
||||
binary.LittleEndian.PutUint32(b[16:], 0x70) // payload size (len - 0x10)
|
||||
binary.LittleEndian.PutUint16(b[20:], 0x00) // requesttype
|
||||
binary.LittleEndian.PutUint16(b[22:], 0x01) // requestreply
|
||||
binary.LittleEndian.PutUint16(b[24:], innerSeq)
|
||||
binary.LittleEndian.PutUint16(b[26:], tokRequest)
|
||||
// token (b[0x20:0x24]) = 0 until the rig grants one
|
||||
copy(b[0x40:0x50], passcode(user))
|
||||
copy(b[0x50:0x60], passcode(pass))
|
||||
nm := name
|
||||
if len(nm) > 16 {
|
||||
nm = nm[:16]
|
||||
}
|
||||
copy(b[0x60:0x70], []byte(nm))
|
||||
return b
|
||||
}
|
||||
|
||||
func parseHeader(b []byte) (length uint32, typ, seq uint16, sentid, rcvdid uint32, ok bool) {
|
||||
if len(b) < 16 {
|
||||
return 0, 0, 0, 0, 0, false
|
||||
@@ -68,22 +127,18 @@ func parseHeader(b []byte) (length uint32, typ, seq uint16, sentid, rcvdid uint3
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("usage: icomnettest <rig-ip> [control-port] [run-seconds]")
|
||||
fmt.Println("example: icomnettest 192.168.1.60 50001 20")
|
||||
fmt.Println("usage: icomnettest <rig-ip> [user] [password]")
|
||||
fmt.Println(" <rig-ip> only → handshake + ping probe")
|
||||
fmt.Println(" <rig-ip> <user> <pass> → also attempt login")
|
||||
fmt.Println("example: icomnettest 192.168.1.60 f6bgc cgb6f1")
|
||||
os.Exit(2)
|
||||
}
|
||||
ip := os.Args[1]
|
||||
port := 50001
|
||||
if len(os.Args) >= 3 {
|
||||
if v, err := strconv.Atoi(os.Args[2]); err == nil {
|
||||
port = v
|
||||
}
|
||||
}
|
||||
runSecs := 20
|
||||
runSecs := 25
|
||||
user, pass := "", ""
|
||||
if len(os.Args) >= 4 {
|
||||
if v, err := strconv.Atoi(os.Args[3]); err == nil && v > 0 {
|
||||
runSecs = v
|
||||
}
|
||||
user, pass = os.Args[2], os.Args[3]
|
||||
}
|
||||
|
||||
target := net.JoinHostPort(ip, strconv.Itoa(port))
|
||||
@@ -110,6 +165,14 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Printf("Probing Icom control stream at %s (myID=0x%08X)\n\n", target, myID)
|
||||
if user != "" {
|
||||
fmt.Printf("Login mode: user=%q pass=%q\n", user, pass)
|
||||
fmt.Printf(" scrambled user = % X\n", passcode(user))
|
||||
fmt.Printf(" scrambled pass = % X\n\n", passcode(pass))
|
||||
}
|
||||
var innerSeq uint16 = 0x0001
|
||||
var tokRequest uint16 = 0x1234 // fixed for reproducibility (no RNG in this probe)
|
||||
loginSent := false
|
||||
|
||||
// 1) areYouThere — ask the rig to announce itself.
|
||||
seq++
|
||||
@@ -154,8 +217,15 @@ func main() {
|
||||
logTx("areYouReady", ctrlPacket(typeAreYouReady, seq, myID, remoteID))
|
||||
readyStarted = true
|
||||
case typeAreYouReady:
|
||||
if readyStarted {
|
||||
fmt.Printf(">> iAmReady — control link is up. (Login is the next milestone.)\n\n")
|
||||
if readyStarted && !loginSent {
|
||||
fmt.Printf(">> iAmReady — control link is up.\n\n")
|
||||
if user != "" {
|
||||
seq++
|
||||
lg := buildLogin(seq, myID, remoteID, innerSeq, tokRequest, user, pass, "OpsLog")
|
||||
fmt.Printf(">> sending login (user=%q)\n", user)
|
||||
logTx("login", lg)
|
||||
loginSent = true
|
||||
}
|
||||
}
|
||||
case typePing:
|
||||
// Reply to the rig's ping: mirror the packet, swap sender/receiver IDs,
|
||||
|
||||
Reference in New Issue
Block a user