feature: JPK encryption and databased events

This commit is contained in:
Matthe815
2023-07-03 02:50:38 -04:00
parent 76a3d73102
commit a065643783
4 changed files with 285 additions and 13 deletions

View File

@@ -138,6 +138,10 @@ func (b *ByteFrame) DataFromCurrent() []byte {
return b.buf[b.index:b.usedSize]
}
func (b *ByteFrame) Index() uint {
return b.index
}
// SetLE sets the byte order to litte endian.
func (b *ByteFrame) SetLE() {
b.byteOrder = binary.LittleEndian

117
common/decryption/jpk.go Normal file
View File

@@ -0,0 +1,117 @@
package decryption
/*
This code is HEAVILY based from
https://github.com/Chakratos/ReFrontier/blob/master/ReFrontier/Unpack.cs
*/
import (
"erupe-ce/common/byteframe"
"io"
)
var m_shiftIndex int = 0
var m_flag byte = byte(0)
func UnpackSimple(data []byte) []byte {
m_shiftIndex = 0
m_flag = byte(0)
bf := byteframe.NewByteFrameFromBytes(data)
bf.SetLE()
header := bf.ReadUint32()
println("Decrypting")
if header == 0x1A524B4A {
bf.Seek(0x2, io.SeekCurrent)
jpkType := bf.ReadUint16()
println("JPK Type: ", jpkType)
switch jpkType {
case 3:
startOffset := bf.ReadInt32()
outSize := bf.ReadInt32()
outBuffer := make([]byte, outSize)
bf.Seek(int64(startOffset), io.SeekStart)
ProcessDecode(bf, outBuffer)
return outBuffer
}
}
println("Skipping")
return data
}
func ProcessDecode(data *byteframe.ByteFrame, outBuffer []byte) {
outIndex := 0
for int(data.Index()) < len(data.Data()) && outIndex < len(outBuffer)-1 {
if JPKBitshift(data) == 0 {
outBuffer[outIndex] = ReadByte(data)
outIndex++
continue
} else {
if JPKBitshift(data) == 0 {
len := (JPKBitshift(data) << 1) | JPKBitshift(data)
off := ReadByte(data)
JPKCopy(outBuffer, int(off), int(len)+3, &outIndex)
continue
} else {
hi := ReadByte(data)
lo := ReadByte(data)
var len int = int((hi & 0xE0)) >> 5
var off int = ((int(hi) & 0x1F) << 8) | int(lo)
if len != 0 {
JPKCopy(outBuffer, off, len+2, &outIndex)
continue
} else {
if JPKBitshift(data) == 0 {
len := (JPKBitshift(data) << 3) | (JPKBitshift(data) << 2) | (JPKBitshift(data) << 1) | JPKBitshift(data)
JPKCopy(outBuffer, off, int(len)+2+8, &outIndex)
continue
} else {
temp := ReadByte(data)
if temp == 0xFF {
for i := 0; i < off+0x1B; i++ {
outBuffer[outIndex] = ReadByte(data)
outIndex++
continue
}
} else {
JPKCopy(outBuffer, off, int(temp)+0x1a, &outIndex)
}
}
}
}
}
}
}
func JPKBitshift(data *byteframe.ByteFrame) byte {
m_shiftIndex--
if m_shiftIndex < 0 {
m_shiftIndex = 7
m_flag = ReadByte(data)
}
return (byte)((m_flag >> m_shiftIndex) & 1)
}
func JPKCopy(outBuffer []byte, offset int, length int, index *int) {
for i := 0; i < length; i++ {
outBuffer[*index] = outBuffer[*index-offset-1]
*index++
}
}
func ReadByte(bf *byteframe.ByteFrame) byte {
value := bf.ReadUint8()
if value < 0 {
println("Not implemented")
}
return byte(value)
}

View File

@@ -0,0 +1,11 @@
BEGIN;
create table if not exists event_quests
(
id serial,
max_players integer,
quest_type integer,
quest_id uint16 not null
);
END;

View File

@@ -1,14 +1,17 @@
package channelserver
import (
"database/sql"
"erupe-ce/common/byteframe"
"erupe-ce/common/decryption"
"erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap"
"io"
"os"
"path/filepath"
"time"
"go.uber.org/zap"
)
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
@@ -79,34 +82,170 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func readOriginalPointers(string_pointer int32, quest []byte) []byte {
file_bytes := byteframe.NewByteFrameFromBytes(quest)
file_bytes.SetLE()
file_bytes.Seek(int64(string_pointer), io.SeekStart)
quest_name_pointer := file_bytes.ReadInt32()
quest_main_pointer := file_bytes.ReadInt32()
quest_a_pointer := file_bytes.ReadInt32()
quest_b_pointer := file_bytes.ReadInt32()
quest_clear_pointer := file_bytes.ReadInt32()
quest_failure_pointer := file_bytes.ReadInt32()
quest_contractor_pointer := file_bytes.ReadInt32()
quest_description_pointer := file_bytes.ReadInt32()
//Use String header pointers to seek to string and then read null terminated string
file_bytes.Seek(int64(quest_name_pointer), io.SeekStart)
quest_name_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_main_pointer), io.SeekStart)
quest_main_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_a_pointer), io.SeekStart)
quest_a_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_b_pointer), io.SeekStart)
quest_b_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_clear_pointer), io.SeekStart)
quest_clear_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_failure_pointer), io.SeekStart)
quest_failure_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_contractor_pointer), io.SeekStart)
quest_contractor_string := file_bytes.ReadNullTerminatedBytes()
file_bytes.Seek(int64(quest_description_pointer), io.SeekStart)
quest_description_string := file_bytes.ReadNullTerminatedBytes()
new_pointers := byteframe.NewByteFrame()
new_pointers.SetLE()
pointer_start := 352
new_pointers.WriteInt32(int32(pointer_start))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + 1))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + len(quest_main_string) + 2))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + len(quest_main_string) + len(quest_a_string) + 3))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + len(quest_main_string) + len(quest_a_string) + len(quest_b_string) + 26))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + len(quest_main_string) + len(quest_a_string) + len(quest_b_string) + len(quest_clear_string) + 5))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + len(quest_main_string) + len(quest_a_string) + len(quest_b_string) + len(quest_clear_string) + len(quest_failure_string) + 6))
new_pointers.WriteInt32(int32(pointer_start + len(quest_name_string) + len(quest_main_string) + len(quest_a_string) + len(quest_b_string) + len(quest_clear_string) + len(quest_failure_string) + len(quest_contractor_string) + 7))
new_pointers.WriteNullTerminatedBytes(quest_name_string)
new_pointers.WriteNullTerminatedBytes(quest_main_string)
new_pointers.WriteNullTerminatedBytes(quest_a_string)
new_pointers.WriteNullTerminatedBytes(quest_b_string)
new_pointers.WriteNullTerminatedBytes(quest_clear_string)
new_pointers.WriteNullTerminatedBytes(quest_failure_string)
new_pointers.WriteNullTerminatedBytes(quest_contractor_string)
new_pointers.WriteNullTerminatedBytes(quest_description_string)
new_pointers.WriteUint8(18)
new_pointers.WriteBytes([]byte{0x83, 0x59, 0x89, 0x5B, 0x83, 0x3A, 0x58, 0xB6, 0x8E, 0x59, 0x82, 0xCC, 0x83, 0x58, 0x83, 0x58, 0x83, 0x81})
return new_pointers.Data()
}
func loadQuestFile(s *Session, quest_id string) []byte {
file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", quest_id)))
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, quest_id))
return nil
}
println(fmt.Sprintf("quests/%s.bin", quest_id))
decrypted := decryption.UnpackSimple(file)
os.WriteFile(fmt.Sprintf("%s.bin", quest_id), decrypted, 0644)
println("Reading quest file " + quest_id)
file_bytes := byteframe.NewByteFrameFromBytes(decrypted)
file_bytes.SetLE()
file_bytes.Seek(0, io.SeekStart)
data_pointer := file_bytes.ReadInt32()
file_bytes.Seek(int64(data_pointer), io.SeekStart)
// Read the quest data.
quest_body := file_bytes.ReadBytes(320)
quest_pointer := byteframe.NewByteFrameFromBytes(quest_body)
quest_pointer.SetLE()
quest_pointer.Seek(40, io.SeekStart)
string_pointer := quest_pointer.ReadInt32()
strings := readOriginalPointers(string_pointer, decrypted)
body := byteframe.NewByteFrame()
body.WriteBytes(quest_body)
body.Seek(0, io.SeekEnd)
body.WriteBytes(strings)
return body.Data()
}
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
var id int32
var max_players, quest_type, quest_id uint16
rows.Scan(&id, &max_players, &quest_type, &quest_id)
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteInt32(id)
bf.WriteInt32(0)
bf.WriteBytes([]byte{0x0F, byte(max_players), byte(quest_type), 0x01})
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteBytes([]byte{0x00, 0x01})
bf.WriteUint16(0)
bf.WriteBytes([]byte{0x02, 0x00})
data := loadQuestFile(s, fmt.Sprintf("%d", quest_id)+"d0")
if data == nil {
return nil, fmt.Errorf("failed to load quest file")
}
bf.WriteBytes(data)
// update checksum
bf.Seek(21, io.SeekStart)
bf.WriteUint8(uint8(len(bf.Data()) - 553))
// overwrite bytes at pos 62
bf.Seek(62, io.SeekStart)
bf.WriteInt16(320)
return bf.Data(), nil
}
func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateQuest)
var totalCount, returnedCount uint16
bf := byteframe.NewByteFrame()
bf.WriteUint16(0)
filepath.Walk(fmt.Sprintf("%s/events/", s.server.erupeConfig.BinPath), func(path string, info os.FileInfo, err error) error {
rows, _ := s.server.db.Query("SELECT id, max_players, quest_type, quest_id FROM event_quests ORDER BY quest_id ASC")
// Loop through each row and load the quest entry if it exists.
for rows.Next() {
var pointer []byte
var max_players, quest_type, checksum, quest_id uint16
rows.Scan(&pointer, &max_players, &quest_type, &checksum, &quest_id)
data, err := makeEventQuest(s, rows)
if err != nil {
return err
} else if info.IsDir() {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
continue
} else {
if len(data) > 850 || len(data) < 400 {
return nil // Could be more or less strict with size limits
continue
} else {
totalCount++
if totalCount > pkt.Offset && len(bf.Data()) < 60000 {
returnedCount++
bf.WriteBytes(data)
return nil
continue
}
}
}
return nil
})
}
type tuneValue struct {
ID uint16
@@ -549,6 +688,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(pkt.Offset)
bf.Seek(0, io.SeekStart)
bf.WriteUint16(returnedCount)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}