Merge branch 'main' of github.com:ZeruLight/Erupe

This commit is contained in:
wish
2023-07-29 17:21:58 +10:00
12 changed files with 2688 additions and 73 deletions

2372
bin/quests/desktop.ini Normal file

File diff suppressed because it is too large Load Diff

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

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

@@ -0,0 +1,109 @@
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 mShiftIndex = 0
var mFlag = byte(0)
func UnpackSimple(data []byte) []byte {
mShiftIndex = 0
mFlag = byte(0)
bf := byteframe.NewByteFrameFromBytes(data)
bf.SetLE()
header := bf.ReadUint32()
if header == 0x1A524B4A {
bf.Seek(0x2, io.SeekCurrent)
jpkType := bf.ReadUint16()
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
}
}
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 {
length := (JPKBitShift(data) << 1) | JPKBitShift(data)
off := ReadByte(data)
JPKCopy(outBuffer, int(off), int(length)+3, &outIndex)
continue
} else {
hi := ReadByte(data)
lo := ReadByte(data)
length := int(hi&0xE0) >> 5
off := ((int(hi) & 0x1F) << 8) | int(lo)
if length != 0 {
JPKCopy(outBuffer, off, length+2, &outIndex)
continue
} else {
if JPKBitShift(data) == 0 {
length := (JPKBitShift(data) << 3) | (JPKBitShift(data) << 2) | (JPKBitShift(data) << 1) | JPKBitShift(data)
JPKCopy(outBuffer, off, int(length)+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 {
mShiftIndex--
if mShiftIndex < 0 {
mShiftIndex = 7
mFlag = ReadByte(data)
}
return (byte)((mFlag >> mShiftIndex) & 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()
return value
}

View File

@@ -17,8 +17,8 @@
"AutoCreateAccount": true,
"CleanDB": false,
"MaxLauncherHR": false,
"LogInboundMessages": false,
"LogOutboundMessages": false,
"LogInboundMessages": true,
"LogOutboundMessages": true,
"MaxHexdumpLength": 256,
"DivaEvent": 0,
"FestaEvent": -1,
@@ -48,6 +48,11 @@
"DailyQuestAllowance": 1,
"MezfesSoloTickets": 10,
"MezfesGroupTickets": 4,
"RegularRavienteMaxPlayers": 8,
"ViolentRavienteMaxPlayers": 8,
"BerserkRavienteMaxPlayers": 32,
"ExtremeRavienteMaxPlayers": 32,
"SmallBerserkRavienteMaxPlayers": 8,
"GUrgentRate": 10,
"GCPMultiplier": 1.00,
"GRPMultiplier": 1.00,
@@ -59,7 +64,8 @@
"EnableKaijiEvent": false,
"EnableHiganjimaEvent": false,
"EnableNierEvent": false,
"DisableRoad": false
"DisableRoad": false,
"SeasonOverride": false
},
"Discord": {
"Enabled": false,

View File

@@ -121,30 +121,36 @@ type SaveDumpOptions struct {
// GameplayOptions has various gameplay modifiers
type GameplayOptions struct {
FeaturedWeapons int // Number of Active Feature weapons to generate daily
MaximumNP int // Maximum number of NP held by a player
MaximumRP uint16 // Maximum number of RP held by a player
MaximumFP uint32 // Maximum number of FP held by a player
DisableLoginBoost bool // Disables the Login Boost system
DisableBoostTime bool // Disables the daily NetCafe Boost Time
BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for
GuildMealDuration int // The number of minutes a Guild Meal can be activated for after cooking
BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily
DailyQuestAllowance uint32 // Number of Daily Quests to allow daily
MezfesSoloTickets uint32 // Number of solo tickets given weekly
MezfesGroupTickets uint32 // Number of group tickets given weekly
GUrgentRate uint16 // Adjusts the rate of G Urgent quests spawning
GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion
GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion
GSRPMultiplier float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion
GZennyMultiplier float32 // Adjusts the multiplier of G Zenny rewarded for quest completion
MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion
ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses
DisableHunterNavi bool // Disables the Hunter Navi
EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar
EnableHiganjimaEvent bool // Enables the Higanjima event in the Rasta Bar
EnableNierEvent bool // Enables the Nier event in the Rasta Bar
DisableRoad bool // Disables the Hunting Road
FeaturedWeapons int // Number of Active Feature weapons to generate daily
MaximumNP int // Maximum number of NP held by a player
MaximumRP uint16 // Maximum number of RP held by a player
MaximumFP uint32 // Maximum number of FP held by a player
DisableLoginBoost bool // Disables the Login Boost system
DisableBoostTime bool // Disables the daily NetCafe Boost Time
BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for
GuildMealDuration int // The number of minutes a Guild Meal can be activated for after cooking
BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily
DailyQuestAllowance uint32 // Number of Daily Quests to allow daily
MezfesSoloTickets uint32 // Number of solo tickets given weekly
MezfesGroupTickets uint32 // Number of group tickets given weekly
RegularRavienteMaxPlayers uint8
ViolentRavienteMaxPlayers uint8
BerserkRavienteMaxPlayers uint8
ExtremeRavienteMaxPlayers uint8
SmallBerserkRavienteMaxPlayers uint8
GUrgentRate uint16 // Adjusts the rate of G Urgent quests spawning
GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion
GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion
GSRPMultiplier float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion
GZennyMultiplier float32 // Adjusts the multiplier of G Zenny rewarded for quest completion
MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion
ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses
DisableHunterNavi bool // Disables the Hunter Navi
EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar
EnableHiganjimaEvent bool // Enables the Higanjima event in the Rasta Bar
EnableNierEvent bool // Enables the Nier event in the Rasta Bar
DisableRoad bool // Disables the Hunting Road
SeasonOverride bool // Overrides the Quest Season with the current Mezeporta Season
}
// Discord holds the discord integration config.

View File

@@ -222,7 +222,7 @@ func main() {
if err != nil {
preventClose(fmt.Sprintf("Channel: Failed to start, %s", err.Error()))
} else {
channelQuery += fmt.Sprintf(`INSERT INTO servers (server_id, season, current_players, world_name, world_description, land) VALUES (%d, %d, 0, '%s', '%s', %d);`, sid, si%3, ee.Name, ee.Description, i+1)
channelQuery += fmt.Sprintf(`INSERT INTO servers (server_id, current_players, world_name, world_description, land) VALUES (%d, 0, '%s', '%s', %d);`, sid, ee.Name, ee.Description, i+1)
channels = append(channels, &c)
logger.Info(fmt.Sprintf("Channel %d (%d): Started successfully", count, ce.Port))
ci++

View File

@@ -0,0 +1,14 @@
BEGIN;
create table if not exists event_quests
(
id serial primary key,
max_players integer,
quest_type integer not null,
quest_id integer not null,
mark integer
);
ALTER TABLE IF EXISTS public.servers DROP COLUMN IF EXISTS season;
END;

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)

View File

@@ -1,14 +1,18 @@
package channelserver
import (
"database/sql"
"erupe-ce/common/byteframe"
"erupe-ce/common/decryption"
ps "erupe-ce/common/pascalstring"
"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) {
@@ -35,29 +39,47 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin")); err == nil {
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin"))
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
s.logger.Debug(
"Quest",
zap.String("Filename", pkt.Filename),
)
}
// Get quest file.
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename))
// This will crash the game.
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
s.logger.Debug(
"Quest",
zap.String("Filename", pkt.Filename),
)
}
if s.server.erupeConfig.GameplayOptions.SeasonOverride {
pkt.Filename = seasonConversion(s, pkt.Filename)
}
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename))
// This will crash the game.
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
}
func questSuffix(s *Session) string {
// Determine the letter to append for day / night
var timeSet string
if TimeGameAbsolute() > 2880 {
timeSet = "d"
} else {
timeSet = "n"
}
return fmt.Sprintf("%s%d", timeSet, s.server.Season())
}
func seasonConversion(s *Session, questFile string) string {
filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s))
// Return original file if file doesn't exist
if _, err := os.Stat(filename); err == nil {
return filename
} else {
return questFile
}
}
@@ -79,34 +101,111 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func loadQuestFile(s *Session, questId int) []byte {
file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId)))
if err != nil {
return nil
}
decrypted := decryption.UnpackSimple(file)
fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
fileBytes.SetLE()
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
// The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320))
questBody.SetLE()
// Find the master quest string pointer
questBody.Seek(40, 0)
fileBytes.Seek(int64(questBody.ReadUint32()), 0)
questBody.Seek(40, 0)
// Overwrite it
questBody.WriteUint32(320)
questBody.Seek(0, 2)
// Rewrite the quest strings and their pointers
var tempString []byte
newStrings := byteframe.NewByteFrame()
tempPointer := 352
for i := 0; i < 8; i++ {
questBody.WriteUint32(uint32(tempPointer))
temp := int64(fileBytes.Index())
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
tempString = fileBytes.ReadNullTerminatedBytes()
fileBytes.Seek(temp+4, 0)
tempPointer += len(tempString) + 1
newStrings.WriteNullTerminatedBytes(tempString)
}
questBody.WriteBytes(newStrings.Data())
return questBody.Data()
}
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
var id, mark uint32
var questId int
var maxPlayers, questType uint8
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark)
data := loadQuestFile(s, questId)
if data == nil {
return nil, fmt.Errorf("failed to load quest file")
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(id)
bf.WriteUint32(0)
bf.WriteUint8(0) // Indexer
switch questType {
case 16:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
case 22:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers)
case 40:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers)
case 50:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers)
case 51:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers)
default:
bf.WriteUint8(maxPlayers)
}
bf.WriteUint8(questType)
bf.WriteUint8(0)
bf.WriteUint16(0)
bf.WriteUint32(mark)
bf.WriteUint16(0)
bf.WriteUint16(uint16(len(data)))
bf.WriteBytes(data)
ps.Uint8(bf, "", true) // What is this string for?
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, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id")
for rows.Next() {
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
if len(data) > 896 || len(data) < 352 {
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
@@ -554,6 +653,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())
}

View File

@@ -405,3 +405,8 @@ func (s *Server) NextSemaphoreID() uint32 {
}
return s.semaphoreIndex
}
func (s *Server) Season() uint8 {
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)
}

View File

@@ -23,3 +23,7 @@ func TimeWeekStart() time.Time {
func TimeWeekNext() time.Time {
return TimeWeekStart().Add(time.Hour * 24 * 7)
}
func TimeGameAbsolute() uint32 {
return uint32((TimeAdjusted().Unix() - 2160) % 5760)
}

View File

@@ -12,9 +12,6 @@ import (
"erupe-ce/server/channelserver"
)
// Server Entries
var season uint8
// Server Channels
var currentplayers uint16
@@ -34,11 +31,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
continue
}
}
sid := (4096 + serverIdx*256) + 16
err := s.db.QueryRow("SELECT season FROM servers WHERE server_id=$1", sid).Scan(&season)
if err != nil {
season = 0
}
sid := (4096 + serverIdx*256) * 6000
if si.IP == "" {
si.IP = config.Host
}
@@ -51,7 +45,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
bf.WriteUint16(0x0000)
bf.WriteUint16(uint16(len(si.Channels)))
bf.WriteUint8(si.Type)
bf.WriteUint8(season)
bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3))
if s.erupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint8(si.Recommended)
}