From 3ba2995afaa8a65c0a12d11f70c4dce0c5ef3140 Mon Sep 17 00:00:00 2001 From: Andrew Gutekanst Date: Tue, 3 Mar 2020 21:13:13 -0500 Subject: [PATCH] Update delta/diff compression impl --- .../compression/deltacomp/deltacomp.go | 86 ++++++++ .../compression/deltacomp/deltacomp_test.go | 113 +++++++++++ .../test_data/hunternavi_0_after.bin | Bin 0 -> 552 bytes .../test_data/hunternavi_0_before.bin | Bin 0 -> 552 bytes .../test_data/hunternavi_0_patch_0.bin | Bin 0 -> 10 bytes .../test_data/hunternavi_0_patch_1.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_after.bin | Bin 0 -> 552 bytes .../test_data/hunternavi_1_before.bin | Bin 0 -> 552 bytes .../test_data/hunternavi_1_patch_0.bin | Bin 0 -> 29 bytes .../test_data/hunternavi_1_patch_1.bin | Bin 0 -> 56 bytes .../test_data/hunternavi_1_patch_10.bin | Bin 0 -> 23 bytes .../test_data/hunternavi_1_patch_11.bin | Bin 0 -> 16 bytes .../test_data/hunternavi_1_patch_12.bin | Bin 0 -> 10 bytes .../test_data/hunternavi_1_patch_13.bin | Bin 0 -> 32 bytes .../test_data/hunternavi_1_patch_14.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_15.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_16.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_17.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_18.bin | Bin 0 -> 5 bytes .../test_data/hunternavi_1_patch_19.bin | Bin 0 -> 5 bytes .../test_data/hunternavi_1_patch_2.bin | Bin 0 -> 8 bytes .../test_data/hunternavi_1_patch_20.bin | Bin 0 -> 5 bytes .../test_data/hunternavi_1_patch_21.bin | Bin 0 -> 5 bytes .../test_data/hunternavi_1_patch_22.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_23.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_24.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_3.bin | Bin 0 -> 10 bytes .../test_data/hunternavi_1_patch_4.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_5.bin | Bin 0 -> 7 bytes .../test_data/hunternavi_1_patch_6.bin | Bin 0 -> 13 bytes .../test_data/hunternavi_1_patch_7.bin | Bin 0 -> 5 bytes .../test_data/hunternavi_1_patch_8.bin | Bin 0 -> 8 bytes .../test_data/hunternavi_1_patch_9.bin | Bin 0 -> 47 bytes .../deltacomp/test_data/platedata_0_after.bin | Bin 0 -> 2752 bytes .../test_data/platedata_0_before.bin | Bin 0 -> 2752 bytes .../test_data/platedata_0_patch_0.bin | Bin 0 -> 8 bytes .../test_data/platedata_0_patch_1.bin | Bin 0 -> 8 bytes .../deltacomp/test_data/savedata_0_after.bin | Bin 0 -> 150820 bytes .../deltacomp/test_data/savedata_0_before.bin | Bin 0 -> 150820 bytes .../test_data/savedata_0_patch_0.bin | Bin 0 -> 143 bytes server/channelserver/handlers.go | 187 ++++++++++++------ 41 files changed, 321 insertions(+), 65 deletions(-) create mode 100644 server/channelserver/compression/deltacomp/deltacomp.go create mode 100644 server/channelserver/compression/deltacomp/deltacomp_test.go create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_0_after.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_0_before.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_0_patch_0.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_0_patch_1.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_after.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_before.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_0.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_1.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_10.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_11.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_12.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_13.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_14.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_15.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_16.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_17.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_18.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_19.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_2.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_20.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_21.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_22.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_23.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_24.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_3.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_4.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_5.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_6.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_7.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_8.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_9.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/platedata_0_after.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/platedata_0_before.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/platedata_0_patch_0.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/platedata_0_patch_1.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/savedata_0_after.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/savedata_0_before.bin create mode 100644 server/channelserver/compression/deltacomp/test_data/savedata_0_patch_0.bin diff --git a/server/channelserver/compression/deltacomp/deltacomp.go b/server/channelserver/compression/deltacomp/deltacomp.go new file mode 100644 index 000000000..4c4cc07a7 --- /dev/null +++ b/server/channelserver/compression/deltacomp/deltacomp.go @@ -0,0 +1,86 @@ +package deltacomp + +import ( + "errors" + + "github.com/Andoryuuta/byteframe" +) + +func checkReadUint8(bf *byteframe.ByteFrame) (uint8, error) { + if len(bf.DataFromCurrent()) >= 1 { + return bf.ReadUint8(), nil + } + return 0, errors.New("Not enough data") +} + +func checkReadUint16(bf *byteframe.ByteFrame) (uint16, error) { + if len(bf.DataFromCurrent()) >= 2 { + return bf.ReadUint16(), nil + } + return 0, errors.New("Not enough data") +} + +func readCount(bf *byteframe.ByteFrame) (int, error) { + var count int + + count8, err := checkReadUint8(bf) + if err != nil { + return 0, err + } + count = int(count8) + + if count == 0 { + count16, err := checkReadUint16(bf) + if err != nil { + return 0, err + } + count = int(count16) + } + + return int(count), nil +} + +// ApplyDataDiff applies a delta data diff patch onto given base data. +func ApplyDataDiff(diff []byte, baseData []byte) []byte { + // Make a copy of the base data to return, + // (probably just make this modify the given slice in the future). + baseCopy := make([]byte, len(baseData)) + copy(baseCopy, baseData) + + patch := byteframe.NewByteFrameFromBytes(diff) + + // The very first matchCount is +1 more than it should be, so we start at -1. + dataOffset := -1 + for { + // Read the amount of matching bytes. + matchCount, err := readCount(patch) + if err != nil { + // No more data + break + } + + dataOffset += matchCount + + // Read the amount of differing bytes. + differentCount, err := readCount(patch) + if err != nil { + // No more data + break + } + differentCount -= 1 + + // Apply the patch bytes. + for i := 0; i < differentCount; i++ { + b, err := checkReadUint8(patch) + if err != nil { + panic("Invalid or misunderstood patch format!") + } + baseCopy[dataOffset+i] = b + } + + dataOffset += differentCount - 1 + + } + + return baseCopy +} diff --git a/server/channelserver/compression/deltacomp/deltacomp_test.go b/server/channelserver/compression/deltacomp/deltacomp_test.go new file mode 100644 index 000000000..246622f3a --- /dev/null +++ b/server/channelserver/compression/deltacomp/deltacomp_test.go @@ -0,0 +1,113 @@ +package deltacomp + +import ( + "bytes" + "encoding/hex" + "fmt" + "io/ioutil" + "testing" + + "github.com/Andoryuuta/Erupe/server/channelserver/compression/nullcomp" +) + +var tests = []struct { + before string + patches []string + after string +}{ + { + "hunternavi_0_before.bin", + []string{ + "hunternavi_0_patch_0.bin", + "hunternavi_0_patch_1.bin", + }, + "hunternavi_0_after.bin", + }, + { + // From "Character Progression 1 Creation-NPCs-Tours" + "hunternavi_1_before.bin", + []string{ + "hunternavi_1_patch_0.bin", + "hunternavi_1_patch_1.bin", + "hunternavi_1_patch_2.bin", + "hunternavi_1_patch_3.bin", + "hunternavi_1_patch_4.bin", + "hunternavi_1_patch_5.bin", + "hunternavi_1_patch_6.bin", + "hunternavi_1_patch_7.bin", + "hunternavi_1_patch_8.bin", + "hunternavi_1_patch_9.bin", + "hunternavi_1_patch_10.bin", + "hunternavi_1_patch_11.bin", + "hunternavi_1_patch_12.bin", + "hunternavi_1_patch_13.bin", + "hunternavi_1_patch_14.bin", + "hunternavi_1_patch_15.bin", + "hunternavi_1_patch_16.bin", + "hunternavi_1_patch_17.bin", + "hunternavi_1_patch_18.bin", + "hunternavi_1_patch_19.bin", + "hunternavi_1_patch_20.bin", + "hunternavi_1_patch_21.bin", + "hunternavi_1_patch_22.bin", + "hunternavi_1_patch_23.bin", + "hunternavi_1_patch_24.bin", + }, + "hunternavi_1_after.bin", + }, + { + // From "Progress Gogo GRP Grind 9 and Armor Upgrades and Partner Equip and Lost Cat and Manager talk and Pugi Order" + // Not really sure this one counts as a valid test as the input and output are exactly the same. The patches cancel each other out. + "platedata_0_before.bin", + []string{ + "platedata_0_patch_0.bin", + "platedata_0_patch_1.bin", + }, + "platedata_0_after.bin", + }, +} + +func readTestDataFile(filename string) []byte { + data, err := ioutil.ReadFile(fmt.Sprintf("./test_data/%s", filename)) + if err != nil { + panic(err) + } + return data +} + +func TestDeltaPatch(t *testing.T) { + for k, tt := range tests { + testname := fmt.Sprintf("delta_patch_test_%d", k) + t.Run(testname, func(t *testing.T) { + // Load the test binary data. + beforeData, err := nullcomp.Decompress(readTestDataFile(tt.before)) + if err != nil { + t.Error(err) + } + + var patches [][]byte + for _, patchName := range tt.patches { + patchData := readTestDataFile(patchName) + patches = append(patches, patchData) + } + + afterData, err := nullcomp.Decompress(readTestDataFile(tt.after)) + if err != nil { + t.Error(err) + } + + // Now actually test calling ApplyDataDiff. + data := beforeData + + // Apply the patches in order. + for i, patch := range patches { + fmt.Println("patch index: ", i) + data = ApplyDataDiff(patch, data) + } + + if !bytes.Equal(data, afterData) { + t.Errorf("got out\n\t%s\nwant\n\t%s", hex.Dump(data), hex.Dump(afterData)) + } + }) + } +} diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_0_after.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_0_after.bin new file mode 100644 index 0000000000000000000000000000000000000000..d4de4341bca1be747f428a19b70293b9171d85e6 GIT binary patch literal 552 zcmZQ%AQmt(ursg#F@$6RvJsFO%;sWXBmkKRT1E{O5SJochiNHwZNU~IAUlz5VZ?L< cnhLB6P|U!tf)gAkpmtE5B}09Ba*9{>OV literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_0_before.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_0_before.bin new file mode 100644 index 0000000000000000000000000000000000000000..3268f9834cfacaeac40bf3c5f7295e0c891b356b GIT binary patch literal 552 zcmZQ%AQmt(ursg#F@$6RvJsFO%;sWXBmkKRT1E{O5SJochiNHwZNU~IAUlz5VZ?L< ZnhLB6P|U!t0_0m5WQN8Lc9oQf0sv8G0TciL literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_0_patch_0.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_0_patch_0.bin new file mode 100644 index 0000000000000000000000000000000000000000..0d460ec60e51f55556b45c9186294e9eb72bad0b GIT binary patch literal 10 RcmZQzJj}!?z{JYH000Ko0OtSz literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_0_patch_1.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_0_patch_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..86b68677a4d6b191e955d42ac28d2d0261e3f846 GIT binary patch literal 7 OcmZQzyv)STzyJUQNdVpe literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_after.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_after.bin new file mode 100644 index 0000000000000000000000000000000000000000..a16e230f0f43ea415f5b64179cf54f1bd7f51772 GIT binary patch literal 552 zcmZQ%AQmt(ursiLF$<7Jh0I_+7Xu>!$VAXGYN&v?6yZWlOQ~xMwr~O2iEIlarX$c) bU{!!(26h#U;8d0w@3h literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_0.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_0.bin new file mode 100644 index 0000000000000000000000000000000000000000..4d4087792ca7cd489be7cc06c68f86f6dd0666f1 GIT binary patch literal 29 UcmeZeVp4)37cc`t0tFcu03S2~#sB~S literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_1.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..ebe6eadcbdc3dee2000f47a3be508373cf7fcc43 GIT binary patch literal 56 VcmZ=~V&q_AWFiIqVPa%p001B10iXZ? literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_10.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_10.bin new file mode 100644 index 0000000000000000000000000000000000000000..2ede80181a8f036b1dfd007df3999f63729dbf67 GIT binary patch literal 23 Rcmcb~#KMG%qL^427yuXT0cijL literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_11.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_11.bin new file mode 100644 index 0000000000000000000000000000000000000000..291775a3bbb36d8fde05542b1a52a1369fbe5015 GIT binary patch literal 16 RcmZQzv}a;rf+AK11^@(r0AK(B literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_12.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_12.bin new file mode 100644 index 0000000000000000000000000000000000000000..28b1ab96c5bd11e709059645c81337c430bca7f5 GIT binary patch literal 10 RcmZQzbZ25=Vq#@r0007#09XJ3 literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_13.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_13.bin new file mode 100644 index 0000000000000000000000000000000000000000..f524a87c7b5ffcb9c93b9b8f8e8ca15e0d99ecba GIT binary patch literal 32 Xcmcc0#F)m!D8R(RgnOI{*Lx literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_2.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_2.bin new file mode 100644 index 0000000000000000000000000000000000000000..ab4f9a69fde689b07c99470b674e18b01de4cbcb GIT binary patch literal 8 PcmZQ%V)S9+VqgFO0mcAb literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_20.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_20.bin new file mode 100644 index 0000000000000000000000000000000000000000..9c0d8cace8c1e174fed491635501fcb7788aa82e GIT binary patch literal 5 McmZ=~VrF0f00DUbO#lD@ literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_21.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_21.bin new file mode 100644 index 0000000000000000000000000000000000000000..4f0d5a3b015f4854ba0dd0d9537312b62f3d37d9 GIT binary patch literal 5 Mcmcc4#LmC~00dP4;Q#;t literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_22.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_22.bin new file mode 100644 index 0000000000000000000000000000000000000000..274523f3744b96793d4e66459a634aea492e28d4 GIT binary patch literal 7 OcmZQzv}IyvU;qFCTL4G^ literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_23.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_23.bin new file mode 100644 index 0000000000000000000000000000000000000000..7e1cf8667fbbc4e44a4f7e744e758dc8a355f9b2 GIT binary patch literal 7 OcmZQzbY)^^U;qFCg8)zf literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_24.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_24.bin new file mode 100644 index 0000000000000000000000000000000000000000..f6796439a77928821da09d8cbe45ce3eaa17c0d8 GIT binary patch literal 7 OcmZQz^kiaZU;qFCmjF}% literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_3.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..830eae49f1d88c65b6dbc961df28378f0c7bb0b7 GIT binary patch literal 10 RcmZQzJj%qv#Kg+L000Jp0M-Bi literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_4.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_4.bin new file mode 100644 index 0000000000000000000000000000000000000000..a034700bc2fa61e2e81b051db9b955cfe3b92dad GIT binary patch literal 7 OcmZQzJjuk$zyJUQ5dhKv literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_5.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_5.bin new file mode 100644 index 0000000000000000000000000000000000000000..342f05115e61372d780f39cfaecd71ee6694091c GIT binary patch literal 7 OcmZQzJj=w&zyJUQB>>g{ literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_6.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_6.bin new file mode 100644 index 0000000000000000000000000000000000000000..98299670068f79358484daf7ce16c9a395331161 GIT binary patch literal 13 RcmZQzyvW4D1VXF~3;+qE0O|k$ literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_7.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_7.bin new file mode 100644 index 0000000000000000000000000000000000000000..05ea1cc7568bc88f1a5a32f7b3747bbd00fa0712 GIT binary patch literal 5 McmdO8Vr5_e008{}H2?qr literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_8.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_8.bin new file mode 100644 index 0000000000000000000000000000000000000000..a96085c826efba7e7977ab18ad8a225d3b5d9419 GIT binary patch literal 8 PcmdOAVqs!pWncgR0mlG8 literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_9.bin b/server/channelserver/compression/deltacomp/test_data/hunternavi_1_patch_9.bin new file mode 100644 index 0000000000000000000000000000000000000000..3844e747cd7005276e1d744b811f38d8b2296fd6 GIT binary patch literal 47 acmdO9V&q_AVZx1Az*0R-tZ$eY85jT@%mNtz literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/platedata_0_after.bin b/server/channelserver/compression/deltacomp/test_data/platedata_0_after.bin new file mode 100644 index 0000000000000000000000000000000000000000..008e412a830898228255aa3c64b1ffc7ca54f83c GIT binary patch literal 2752 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 literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/savedata_0_before.bin b/server/channelserver/compression/deltacomp/test_data/savedata_0_before.bin new file mode 100644 index 0000000000000000000000000000000000000000..9a6ec60a2ee652693aa2fef63f9382968648f767 GIT binary patch literal 150820 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 G!2bi@^Txse literal 0 HcmV?d00001 diff --git a/server/channelserver/compression/deltacomp/test_data/savedata_0_patch_0.bin b/server/channelserver/compression/deltacomp/test_data/savedata_0_patch_0.bin new file mode 100644 index 0000000000000000000000000000000000000000..de039d4f6823fa2974cdd8cfd9c96ebe97ad3afd GIT binary patch literal 143 zcmZSR{mp*gVx`$ivugIoR*!&Cn;pbk4dg$vdSc~jUd_JC1_T-UWLZM&8SPj+Y#A9e z4=^z@Ff}l-Ffe{#Vq#!5V_^US4h<$|0VYNZknjyACJvwsQvr~euE)&2X(_Y169Xd` jGj9|V(+wuJ7aUhP9xySTWntd7jDhtk>lO1hdj 2 { - if bytes.IndexRune(b, 2) != 0 { - seekBytes = b[:bytes.IndexRune(b, 2)+1] - } else { - seekBytes = b[:bytes.IndexRune(b[1:], 2)+2] - } - if len(seekBytes) == 1 { - seekBytes = b[:bytes.IndexRune(b, 2)+2] - //fmt.Printf("Seek: %d SeekBytes: %X Write: %X\n", seekBytes[0], seekBytes, b[len(seekBytes)] ) - seekOperation += int(seekBytes[0]) - write = b[len(seekBytes)] - b = b[3:] - } else { - seek := int32(0) - for _, b := range seekBytes[:len(seekBytes)-1] { - seek = (seek << 8) | int32(b) - } - //fmt.Printf("Seek: %d SeekBytes: %X Write: %X\n", seek, seekBytes, b[len(seekBytes)] ) - seekOperation += int(seek) - write = b[len(seekBytes)] - b = b[len(seekBytes)+1:] - } - save[seekOperation-1] = write - } - - return save -} - func updateRights(s *Session) { update := &mhfpacket.MsgSysUpdateRight{ Unk0: 0, @@ -930,28 +896,80 @@ func handleMsgSysReserve5F(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavedata) + err := ioutil.WriteFile(fmt.Sprintf("savedata\\%d.bin", time.Now().Unix()), pkt.RawDataPayload, 0644) if err != nil { s.logger.Fatal("Error dumping savedata", zap.Error(err)) } - if pkt.SaveType == 2 { - _, err = s.server.db.Exec("UPDATE characters SET is_new_character=false, savedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) - // Temporary server launcher response stuff - // 0x1F715 Weapon Class - // 0x1FDF6 HR (small_gr_level) - // 0x88 Character Name - saveFile, _ := nullcomp.Decompress(pkt.RawDataPayload) - _, err = s.server.db.Exec("UPDATE characters SET weapon=$1 WHERE id=$2", uint16(saveFile[128789]), s.charID) - x := uint16(saveFile[130550])<<8 | uint16(saveFile[130551]) - _, err = s.server.db.Exec("UPDATE characters SET small_gr_level=$1 WHERE id=$2", uint16(x), s.charID) - _, err = s.server.db.Exec("UPDATE characters SET name=$1 WHERE id=$2", strings.SplitN(string(saveFile[88:100]), "\x00", 2)[0], s.charID) + // Var to hold the decompressed savedata for updating the launcher response fields. + var decompressedData []byte + + if pkt.SaveType == 1 { + // Diff-based update. + + // Load existing save + var data []byte + err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data) + if err != nil { + s.logger.Fatal("Failed to get savedata from db", zap.Error(err)) + } + + // Decompress + s.logger.Info("Decompressing...") + data, err = nullcomp.Decompress(data) + if err != nil { + s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err)) + } + + // Perform diff and compress it to write back to db + s.logger.Info("Diffing...") + saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)) + if err != nil { + s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err)) + } + + decompressedData = saveOutput // For updating launcher fields. + + _, err = s.server.db.Exec("UPDATE characters SET savedata=$1 WHERE id=$2", saveOutput, s.charID) + if err != nil { + s.logger.Fatal("Failed to update savedata in db", zap.Error(err)) + } + + s.logger.Info("Wrote recompressed savedata back to DB.") + } else { + // Regular blob update. + + _, err = s.server.db.Exec("UPDATE characters SET is_new_character=false, savedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) if err != nil { s.logger.Fatal("Failed to update savedata in db", zap.Error(err)) } - } else { - fmt.Printf("Got savedata packet of type 1, no handling implemented. Not saving.") + + decompressedData, err = nullcomp.Decompress(pkt.RawDataPayload) // For updating launcher fields. + if err != nil { + s.logger.Fatal("Failed to decompress savedata from packet", zap.Error(err)) + } + } + + // Temporary server launcher response stuff + // 0x1F715 Weapon Class + // 0x1FDF6 HR (small_gr_level) + // 0x88 Character Name + _, err = s.server.db.Exec("UPDATE characters SET weapon=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID) + if err != nil { + s.logger.Fatal("Failed to character weapon in db", zap.Error(err)) + } + + x := uint16(decompressedData[130550])<<8 | uint16(decompressedData[130551]) + _, err = s.server.db.Exec("UPDATE characters SET small_gr_level=$1 WHERE id=$2", uint16(x), s.charID) + if err != nil { + s.logger.Fatal("Failed to character small_gr_level in db", zap.Error(err)) + } + + _, err = s.server.db.Exec("UPDATE characters SET name=$1 WHERE id=$2", strings.SplitN(string(decompressedData[88:100]), "\x00", 2)[0], s.charID) + if err != nil { + s.logger.Fatal("Failed to character name in db", zap.Error(err)) } s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) @@ -1532,34 +1550,41 @@ func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateData) + err := ioutil.WriteFile(fmt.Sprintf("savedata\\%d_platedata.bin", time.Now().Unix()), pkt.RawDataPayload, 0644) if err != nil { s.logger.Fatal("Error dumping platedata", zap.Error(err)) } + if pkt.IsDataDiff { - // https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf var data []byte - //load existing save + + // Load existing save err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { s.logger.Fatal("Failed to get platedata savedata from db", zap.Error(err)) } - //decompress - fmt.Println("Decompressing...") + + // Decompress + s.logger.Info("Decompressing...") data, err = nullcomp.Decompress(data) if err != nil { s.logger.Fatal("Failed to decompress platedata from db", zap.Error(err)) } - // perform diff and compress it to write back to db - fmt.Println("Diffing...") - saveOutput, err := nullcomp.Compress(saveDataDiff(pkt.RawDataPayload, data)) + + // Perform diff and compress it to write back to db + s.logger.Info("Diffing...") + saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)) if err != nil { s.logger.Fatal("Failed to diff and compress platedata savedata", zap.Error(err)) } + _, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID) if err != nil { s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) } + + s.logger.Info("Wrote recompressed platedata back to DB.") } else { // simply update database, no extra processing _, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) @@ -1567,6 +1592,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err)) } } + s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) } @@ -1587,36 +1613,41 @@ func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateBox) + err := ioutil.WriteFile(fmt.Sprintf("savedata\\%d_platebox.bin", time.Now().Unix()), pkt.RawDataPayload, 0644) if err != nil { s.logger.Fatal("Error dumping hunter platebox savedata", zap.Error(err)) } + if pkt.IsDataDiff { var data []byte - //load existing save + + // Load existing save err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data) if err != nil { s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err)) } - //decompress - fmt.Println("Decompressing...") + + // Decompress + s.logger.Info("Decompressing...") data, err = nullcomp.Decompress(data) if err != nil { s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err)) } - // perform diff and compress it to write back to db - fmt.Println("Diffing...") - saveOutput, err := nullcomp.Compress(saveDataDiff(pkt.RawDataPayload, data)) + + // Perform diff and compress it to write back to db + s.logger.Info("Diffing...") + saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)) if err != nil { s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err)) } + _, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID) if err != nil { s.logger.Fatal("Failed to update platebox savedata in db", zap.Error(err)) - } else { - fmt.Println("Wrote recompressed save back to DB.") } + s.logger.Info("Wrote recompressed platebox back to DB.") } else { // simply update database, no extra processing _, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) @@ -1911,8 +1942,34 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) { } if pkt.IsDataDiff { - // https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf - // doesn't seem fully consistent with platedata? + var data []byte + + // Load existing save + err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data) + if err != nil { + s.logger.Fatal("Failed to get hunternavi savedata from db", zap.Error(err)) + } + + // Decompress + s.logger.Info("Decompressing...") + data, err = nullcomp.Decompress(data) + if err != nil { + s.logger.Fatal("Failed to decompress hunternavi from db", zap.Error(err)) + } + + // Perform diff and compress it to write back to db + s.logger.Info("Diffing...") + saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)) + if err != nil { + s.logger.Fatal("Failed to diff and compress hunternavi savedata", zap.Error(err)) + } + + _, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID) + if err != nil { + s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err)) + } + + s.logger.Info("Wrote recompressed hunternavi back to DB.") } else { // simply update database, no extra processing _, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)