This commit is contained in:
SirFist
2020-03-09 00:32:36 +00:00
39 changed files with 427 additions and 155 deletions

View File

@@ -0,0 +1,91 @@
package deltacomp
import (
"bytes"
"io"
)
func checkReadUint8(r *bytes.Reader) (uint8, error) {
b, err := r.ReadByte()
if err != nil {
return 0, err
}
return b, nil
}
func checkReadUint16(r *bytes.Reader) (uint16, error) {
data := make([]byte, 2)
n, err := r.Read(data)
if err != nil {
return 0, err
} else if n != len(data) {
return 0, io.EOF
}
return uint16(data[0])<<8 | uint16(data[1]), nil
}
func readCount(r *bytes.Reader) (int, error) {
var count int
count8, err := checkReadUint8(r)
if err != nil {
return 0, err
}
count = int(count8)
if count == 0 {
count16, err := checkReadUint16(r)
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 := bytes.NewReader(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--
// 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
}

View File

@@ -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))
}
})
}
}

View File

@@ -0,0 +1,93 @@
package nullcomp
import (
"bytes"
"io"
)
// Decompress decompresses null-compressesed data.
func Decompress(compData []byte) ([]byte, error) {
r := bytes.NewReader(compData)
header := make([]byte, 16)
n, err := r.Read(header)
if err != nil {
return nil, err
} else if n != len(header) {
return nil, err
}
// Just return the data if it doesn't contain the cmp header.
if !bytes.Equal(header, []byte("cmp\x2020110113\x20\x20\x20\x00")) {
return compData, nil
}
var output []byte
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == 0 {
// If it's a null byte, then the next byte is how many nulls to add.
nullCount, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
output = append(output, make([]byte, int(nullCount))...)
} else {
output = append(output, b)
}
}
return output, nil
}
// Compress null compresses give given data.
func Compress(rawData []byte) ([]byte, error) {
r := bytes.NewReader(rawData)
var output []byte
output = append(output, []byte("cmp\x2020110113\x20\x20\x20\x00")...)
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == 0 {
output = append(output, []byte{0x00}...)
// read to get null count
nullCount := 1
for {
i, err := r.ReadByte()
if err == io.EOF {
output = append(output, []byte{byte(nullCount)}...)
break
} else if i != 0 {
r.UnreadByte()
output = append(output, []byte{byte(nullCount)}...)
break
} else if err != nil {
return nil, err
}
nullCount++
// Flush the null-count if it gets to 255, start on the next null count.
if nullCount == 255 {
output = append(output, []byte{0xFF, 0x00}...)
nullCount = 0
}
}
} else {
output = append(output, b)
}
}
return output, nil
}

View File

@@ -6,7 +6,6 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -14,6 +13,8 @@ import (
"time"
"github.com/Andoryuuta/Erupe/network/mhfpacket"
"github.com/Andoryuuta/Erupe/server/channelserver/compression/deltacomp"
"github.com/Andoryuuta/Erupe/server/channelserver/compression/nullcomp"
"github.com/Andoryuuta/byteframe"
"go.uber.org/zap"
"golang.org/x/text/encoding/japanese"
@@ -53,127 +54,6 @@ func doSizedAckResp(s *Session, ackHandle uint32, data []byte) {
s.QueueAck(ackHandle, bfw.Data())
}
// process a datadiff save for platebox and platedata
func saveDataDiff(b []byte, save []byte) []byte {
// there are a bunch of extra variations on this method in use which this does not handle yet
// specifically this is for diffs with seek amounts trailed by 02 followed by bytes to be written
var seekBytes []byte
seekOperation := 0
write := byte(0)
for len(b) > 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
}
// decompress save data
func saveDecompress(compData []byte) ([]byte, error) {
r := bytes.NewReader(compData)
header := make([]byte, 16)
n, err := r.Read(header)
if err != nil {
return nil, err
} else if n != len(header) {
return nil, err
}
if !bytes.Equal(header, []byte("cmp\x2020110113\x20\x20\x20\x00")) {
return nil, err
}
var output []byte
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == 0 {
// If it's a null byte, then the next byte is how many nulls to add.
nullCount, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
output = append(output, make([]byte, int(nullCount))...)
} else {
output = append(output, b)
}
}
return output, nil
}
// Null compresses a save
func saveCompress(rawData []byte) ([]byte, error) {
r := bytes.NewReader(rawData)
var output []byte
output = append(output, []byte("cmp\x2020110113\x20\x20\x20\x00")...)
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if b == 0 {
output = append(output, []byte{0x00}...)
// read to get null count
nullCount := 1
for {
i, err := r.ReadByte()
if err == io.EOF {
output = append(output, []byte{byte(nullCount)}...)
break
} else if i != 0 {
r.UnreadByte()
output = append(output, []byte{byte(nullCount)}...)
break
} else if err != nil {
return nil, err
}
nullCount++
if nullCount == 255 {
output = append(output, []byte{0xFF, 0x00}...)
nullCount = 0
}
}
//output = append(output, []byte{byte(nullCount)}...)
} else {
output = append(output, b)
}
}
return output, nil
}
func updateRights(s *Session) {
update := &mhfpacket.MsgSysUpdateRight{
Unk0: 0,
@@ -1023,28 +903,88 @@ 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, _ := saveDecompress(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.
data = deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
// Make a copy for updating the launcher fields.
decompressedData = make([]byte, len(data))
copy(decompressedData, data)
// Compress it to write back to db
s.logger.Info("Diffing...")
saveOutput, err := nullcomp.Compress(data)
if err != nil {
s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err))
}
_, 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))
}
gr := uint16(decompressedData[130550])<<8 | uint16(decompressedData[130551])
s.logger.Info("Setting db field", zap.Uint16("gr_override_level", gr))
// We have to use `gr_override_level` (uint16), not `small_gr_level` (uint8) to store this.
_, err = s.server.db.Exec("UPDATE characters SET gr_override_mode=true, gr_override_level=$1 WHERE id=$2", gr, s.charID)
if err != nil {
s.logger.Fatal("Failed to update character gr_override_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 update character name in db", zap.Error(err))
}
s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
@@ -1658,34 +1598,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...")
data, err = saveDecompress(data)
// 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 := saveCompress(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)
@@ -1693,6 +1640,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})
}
@@ -1713,36 +1661,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...")
data, err = saveDecompress(data)
// 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 := saveCompress(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)
@@ -2020,7 +1973,6 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
if len(data) > 0 {
doSizedAckResp(s, pkt.AckHandle, data)
//doSizedAckResp(s, pkt.AckHandle, data)
} else {
// set first byte to 1 to avoid pop up every time without save
body := make([]byte, 0x226)
@@ -2037,8 +1989,31 @@ 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))
}
// Check if we actually had any hunternavi data, using a blank buffer if not.
// This is requried as the client will try to send a diff after character creation without a prior MsgMhfSaveHunterNavi packet.
if len(data) == 0 {
data = make([]byte, 0x226)
data[0] = 1 // set first byte to 1 to avoid pop up every time without save
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
_, 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)