package sqex import ( "encoding/hex" "strings" "testing" ) // Vectors below were produced by an independent line-by-line Python // translation of FFXIVQuickLauncher's LegacyBlowfish/ArgumentBuilder (the // production C# that the game accepts). SE's "legacy" blowfish is NOT plain // textbook blowfish, so these known-good outputs are the source of truth. func TestEncryptMatchesReference(t *testing.T) { data := []byte("/T =12345678 /DEV.DataPathType =1 /SYS.Region =1") cases := []struct { key string want string }{ {"0a1b2c3d", "88d89fd7953c59465244c1fc88fb504ef0223510e60af3d0f9babfa42c23e183aa0b0573c3549451d5f5530cc1a107d1"}, {"f", "2b7a393a2488f10e50db5bdabe3ca9eb3694eb399874ffe79d8528637832aefacb1b05170770ed3a26107a10b70feee4"}, {"5abc9f01", "ce38d1ec86a772d80258790506e40a8df9d24cbd0bd31975c3bb4f90079f8c127228fd9d32672222fc44b176694e56fa"}, } for _, c := range cases { got := New([]byte(c.key)).Encrypt(data) if hex.EncodeToString(got) != c.want { t.Errorf("key %q:\n got %x\n want %s", c.key, got, c.want) } } } func TestEncryptPadding(t *testing.T) { // 30-byte input must pad UP to 32 (a signed-mod bug once padded it down to 24). out := New([]byte("0a1b2c3d")).Encrypt(make([]byte, 30)) if len(out) != 32 { t.Fatalf("want 32-byte output for 30-byte input, got %d", len(out)) } if out := New([]byte("f")).Encrypt(make([]byte, 8)); len(out) != 8 { t.Fatalf("exact multiple of 8 must not grow") } } func TestMangledBase64(t *testing.T) { // base64 of 0xfb 0xef is "++8=" -> mangled "--8*" if got := MangledSeBase64([]byte{0xfb, 0xef}); got != "--8*" { t.Fatalf("mangling wrong: %q", got) } // '/' case: 0x3b 0xf1 base64 is "O/E=" -> mangled "O_E*" if got := MangledSeBase64([]byte{0x3b, 0xf1}); got != "O_E*" { t.Fatalf("mangling wrong: %q", got) } } func TestBuildEncrypted(t *testing.T) { // Full-string vectors from the Python reference. The checksum char is // table[(key&0xF0000)>>16]; keys 2 and 3 also pin the zero-padded %08x key // (C# $"{{key:x08}}") — a bare %x would drop the leading '0'. args := [][2]string{{"T", "1234"}, {"DEV.DataPathType", "1"}} for _, c := range []struct { key uint32 want string }{ {0x5ABC0000, "//**sqex0003J4T_RN36xpQUM0mF76Vlc4So-8x6_ef013XjtzA9VUE*4**//"}, // 0xC -> '4' {0x0FAB0000, "//**sqex0003FF-C3DaxuXSZDFry7v1qrgdN2Ghnm0Yplk6hsrcem3Q*P**//"}, // 0xB -> 'P' {0x01230000, "//**sqex0003B6Tm3_-ANOlV75JWVE70aJMa2L5DF70PWuOjvEPCOQ4*p**//"}, // 0x3 -> 'p' } { got := BuildEncrypted(c.key, args) if got != c.want { t.Errorf("key 0x%08x:\n got %s\n want %s", c.key, got, c.want) } } got := BuildEncrypted(0x5ABC0000, args) for _, bad := range []string{"+", "/", "="} { if strings.Contains(got[10:len(got)-4], bad) { t.Errorf("unmangled char %q in body: %s", bad, got) } } }