mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 15:43:49 +01:00
Port ECD encryption/decryption from ReFrontier (C#) and FrontierTextHandler (Python) into common/decryption. The cipher uses a 32-bit LCG key stream with an 8-round Feistel-like nibble transformation and CFB chaining; all six key sets are supported, key 4 being the default for all MHF files. On startup, loadRengokuBinary now decrypts (ECD) and decompresses (JKR) the binary to validate pointer bounds and entry counts, then logs a structured summary (floor counts, spawn table counts, unique monster IDs). Failures are non-fatal — the encrypted blob is still cached and served to clients unchanged, preserving existing behaviour. Closes #173.
141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package decryption
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
// TestEncodeDecodeECD_RoundTrip verifies that encoding then decoding returns
|
|
// the original plaintext for various payloads and key indices.
|
|
func TestEncodeDecodeECD_RoundTrip(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
payload []byte
|
|
key int
|
|
}{
|
|
{"empty", []byte{}, DefaultECDKey},
|
|
{"single_byte", []byte{0x42}, DefaultECDKey},
|
|
{"all_zeros", make([]byte, 64), DefaultECDKey},
|
|
{"all_ones", bytes.Repeat([]byte{0xFF}, 64), DefaultECDKey},
|
|
{"sequential", func() []byte {
|
|
b := make([]byte, 256)
|
|
for i := range b {
|
|
b[i] = byte(i)
|
|
}
|
|
return b
|
|
}(), DefaultECDKey},
|
|
{"key0", []byte("hello world"), 0},
|
|
{"key1", []byte("hello world"), 1},
|
|
{"key5", []byte("hello world"), 5},
|
|
{"large", bytes.Repeat([]byte("MHFrontier"), 1000), DefaultECDKey},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
enc, err := EncodeECD(tc.payload, tc.key)
|
|
if err != nil {
|
|
t.Fatalf("EncodeECD: %v", err)
|
|
}
|
|
|
|
// Encoded output must start with ECD magic.
|
|
if len(enc) < 4 {
|
|
t.Fatalf("encoded output too short: %d bytes", len(enc))
|
|
}
|
|
|
|
dec, err := DecodeECD(enc)
|
|
if err != nil {
|
|
t.Fatalf("DecodeECD: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(dec, tc.payload) {
|
|
t.Errorf("round-trip mismatch:\n got %x\n want %x", dec, tc.payload)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDecodeECD_Errors verifies that invalid inputs are rejected with errors.
|
|
func TestDecodeECD_Errors(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
data []byte
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "too_small",
|
|
data: []byte{0x65, 0x63, 0x64},
|
|
wantErr: "too small",
|
|
},
|
|
{
|
|
name: "bad_magic",
|
|
data: func() []byte {
|
|
b := make([]byte, 16)
|
|
b[0] = 0xDE
|
|
return b
|
|
}(),
|
|
wantErr: "invalid magic",
|
|
},
|
|
{
|
|
name: "invalid_key",
|
|
data: func() []byte {
|
|
b := make([]byte, 16)
|
|
// ECD magic
|
|
b[0], b[1], b[2], b[3] = 0x65, 0x63, 0x64, 0x1A
|
|
// key index = 99 (out of range)
|
|
b[4] = 99
|
|
return b
|
|
}(),
|
|
wantErr: "invalid key",
|
|
},
|
|
{
|
|
name: "payload_exceeds_buffer",
|
|
data: func() []byte {
|
|
b := make([]byte, 16)
|
|
b[0], b[1], b[2], b[3] = 0x65, 0x63, 0x64, 0x1A
|
|
// key 4
|
|
b[4] = DefaultECDKey
|
|
// declare payload size larger than the buffer
|
|
b[8], b[9], b[10], b[11] = 0xFF, 0xFF, 0xFF, 0x00
|
|
return b
|
|
}(),
|
|
wantErr: "exceeds buffer",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := DecodeECD(tc.data)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !bytes.Contains([]byte(err.Error()), []byte(tc.wantErr)) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), tc.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestEncodeECD_InvalidKey verifies that an out-of-range key is rejected.
|
|
func TestEncodeECD_InvalidKey(t *testing.T) {
|
|
_, err := EncodeECD([]byte("test"), 99)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid key, got nil")
|
|
}
|
|
}
|
|
|
|
// TestDecodeECD_EmptyPayload verifies that a valid header with zero payload
|
|
// decodes to an empty slice without error.
|
|
func TestDecodeECD_EmptyPayload(t *testing.T) {
|
|
enc, err := EncodeECD([]byte{}, DefaultECDKey)
|
|
if err != nil {
|
|
t.Fatalf("EncodeECD: %v", err)
|
|
}
|
|
dec, err := DecodeECD(enc)
|
|
if err != nil {
|
|
t.Fatalf("DecodeECD: %v", err)
|
|
}
|
|
if len(dec) != 0 {
|
|
t.Errorf("expected empty payload, got %d bytes", len(dec))
|
|
}
|
|
}
|