6 Commits

Author SHA1 Message Date
wish
bf86424541 add support for return guilds 2023-04-11 21:09:03 +10:00
wish
23138b2d5b update versioning 2023-04-11 21:08:00 +10:00
wish
3be014ba7f remove unused code 2023-04-11 21:07:43 +10:00
wish
4ffb176049 prevent reading past message board packet 2023-04-10 18:52:12 +10:00
wish
b0d53431c0 Merge pull request #65 from BlackhawkGT/main
Fix pointerGender offset
2023-04-07 19:40:15 +10:00
Sophie R
13522ef2c9 Fix pointerGender offset being wrong
Every character seems to be assigned as male no matter what and changing the offset to 0x51 fixes this.
2023-04-06 14:44:27 +02:00
8 changed files with 102 additions and 99 deletions

View File

@@ -2,62 +2,14 @@ package stringsupport
import (
"bytes"
"io/ioutil"
"io"
"strconv"
"strings"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
)
// StringConverter is a small helper for encoding/decoding strings.
type StringConverter struct {
Encoding encoding.Encoding
}
// Decode decodes the given bytes as the set encoding.
func (sc *StringConverter) Decode(data []byte) (string, error) {
decoded, err := ioutil.ReadAll(transform.NewReader(bytes.NewBuffer(data), sc.Encoding.NewDecoder()))
if err != nil {
return "", err
}
return string(decoded), nil
}
// MustDecode decodes the given bytes as the set encoding. Panics on decode failure.
func (sc *StringConverter) MustDecode(data []byte) string {
decoded, err := sc.Decode(data)
if err != nil {
panic(err)
}
return decoded
}
// Encode encodes the given string as the set encoding.
func (sc *StringConverter) Encode(data string) ([]byte, error) {
encoded, err := ioutil.ReadAll(transform.NewReader(bytes.NewBuffer([]byte(data)), sc.Encoding.NewEncoder()))
if err != nil {
return nil, err
}
return encoded, nil
}
// MustEncode encodes the given string as the set encoding. Panics on encode failure.
func (sc *StringConverter) MustEncode(data string) []byte {
encoded, err := sc.Encode(data)
if err != nil {
panic(err)
}
return encoded
}
func UTF8ToSJIS(x string) []byte {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
@@ -69,7 +21,7 @@ func UTF8ToSJIS(x string) []byte {
func SJISToUTF8(b []byte) string {
d := japanese.ShiftJIS.NewDecoder()
result, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(b), d))
result, err := io.ReadAll(transform.NewReader(bytes.NewReader(b), d))
if err != nil {
panic(err)
}

View File

@@ -5,7 +5,7 @@
"DisableSoftCrash": false,
"HideLoginNotice": true,
"LoginNotices": [
"<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9.2!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>this wouldn't exist without you."
"<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9.3!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>this wouldn't exist without you."
],
"PatchServerManifest": "",
"PatchServerFile": "",

View File

@@ -54,7 +54,7 @@ func main() {
defer zapLogger.Sync()
logger := zapLogger.Named("main")
logger.Info(fmt.Sprintf("Starting Erupe (9.2-%s)", Commit()))
logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit()))
if config.ErupeConfig.Database.Password == "" {
preventClose("Database password is blank")

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/stringsupport"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -10,9 +11,16 @@ import (
// MsgMhfUpdateGuildMessageBoard represents the MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD
type MsgMhfUpdateGuildMessageBoard struct {
AckHandle uint32
MessageOp uint32
Request []byte
AckHandle uint32
MessageOp uint32
PostType uint32
StampID uint32
TitleLength uint32
BodyLength uint32
Title string
Body string
PostID uint32
LikeState bool
}
// Opcode returns the ID associated with this packet type.
@@ -24,9 +32,31 @@ func (m *MsgMhfUpdateGuildMessageBoard) Opcode() network.PacketID {
func (m *MsgMhfUpdateGuildMessageBoard) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.MessageOp = bf.ReadUint32()
if m.MessageOp != 5 {
m.Request = bf.DataFromCurrent()
bf.Seek(int64(len(bf.Data())-2), 0)
switch m.MessageOp {
case 0:
m.PostType = bf.ReadUint32()
m.StampID = bf.ReadUint32()
m.TitleLength = bf.ReadUint32()
m.BodyLength = bf.ReadUint32()
m.Title = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength)))
m.Body = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength)))
case 1:
m.PostID = bf.ReadUint32()
case 2:
m.PostID = bf.ReadUint32()
bf.ReadBytes(8)
m.TitleLength = bf.ReadUint32()
m.BodyLength = bf.ReadUint32()
m.Title = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength)))
m.Body = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength)))
case 3:
m.PostID = bf.ReadUint32()
bf.ReadBytes(8)
m.StampID = bf.ReadUint32()
case 4:
m.PostID = bf.ReadUint32()
bf.ReadBytes(8)
m.LikeState = bf.ReadBool()
}
return nil
}

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE public.guilds ADD COLUMN IF NOT EXISTS return_type INTEGER DEFAULT 0;
END;

View File

@@ -12,7 +12,7 @@ import (
)
const (
pointerGender = 0x81 // +1
pointerGender = 0x51 // +1
pointerRP = 0x22D16 // +2
pointerHouseTier = 0x1FB6C // +5
pointerHouseData = 0x1FE01 // +195

View File

@@ -52,6 +52,7 @@ type Guild struct {
RankRP uint32 `db:"rank_rp"`
EventRP uint32 `db:"event_rp"`
Comment string `db:"comment"`
ReturnType uint8 `db:"return_type"`
PugiName1 string `db:"pugi_name_1"`
PugiName2 string `db:"pugi_name_2"`
PugiName3 string `db:"pugi_name_3"`
@@ -127,6 +128,7 @@ SELECT
leader_id,
lc.name as leader_name,
comment,
return_type,
COALESCE(pugi_name_1, '') AS pugi_name_1,
COALESCE(pugi_name_2, '') AS pugi_name_2,
COALESCE(pugi_name_3, '') AS pugi_name_3,
@@ -445,7 +447,7 @@ func (guild *Guild) HasApplicationForCharID(s *Session, charID uint32) (bool, er
return true, nil
}
func CreateGuild(s *Session, guildName string) (int32, error) {
func CreateGuild(s *Session, guildName string) (uint32, error) {
transaction, err := s.server.db.Begin()
if err != nil {
@@ -468,7 +470,7 @@ func CreateGuild(s *Session, guildName string) (int32, error) {
return 0, err
}
var guildId int32
var guildId uint32
guildResult.Next()
@@ -606,7 +608,7 @@ func handleMsgMhfCreateGuild(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(guildId))
bf.WriteUint32(guildId)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -933,12 +935,16 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBool(!guild.Recruiting)
if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00)
} else if guild.LeaderCharID == s.charID {
bf.WriteUint16(0x01)
if guild.ReturnType > 0 {
bf.WriteUint16(0x0F)
} else {
bf.WriteUint16(0x02)
if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00)
} else if guild.LeaderCharID == s.charID {
bf.WriteUint16(0x01)
} else {
bf.WriteUint16(0x02)
}
}
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
@@ -953,9 +959,9 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(guild.RankRP)
bf.WriteBytes(guildLeaderName)
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk
bf.WriteBool(false) // isReturnGuild
bf.WriteBool(false) // earnedSpecialHall
bf.WriteBytes([]byte{0x02, 0x02}) // Unk
bf.WriteUint8(guild.ReturnType)
bf.WriteBool(false) // earnedSpecialHall
bf.WriteBytes([]byte{0x02, 0x02}) // Unk
bf.WriteUint32(guild.EventRP)
ps.Uint8(bf, guild.PugiName1, true)
ps.Uint8(bf, guild.PugiName2, true)
@@ -1867,7 +1873,6 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateGuildMessageBoard)
bf := byteframe.NewByteFrameFromBytes(pkt.Request)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
applicant := false
if guild != nil {
@@ -1879,45 +1884,26 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
}
switch pkt.MessageOp {
case 0: // Create message
postType := bf.ReadUint32() // 0 = message, 1 = news
stampID := bf.ReadUint32()
titleLength := bf.ReadUint32()
bodyLength := bf.ReadUint32()
title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength)))
body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength)))
s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, stampID, postType, title, body)
s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, pkt.StampID, pkt.PostType, pkt.Title, pkt.Body)
// TODO: if there are too many messages, purge excess
case 1: // Delete message
postID := bf.ReadUint32()
s.server.db.Exec("DELETE FROM guild_posts WHERE id = $1", postID)
s.server.db.Exec("DELETE FROM guild_posts WHERE id = $1", pkt.PostID)
case 2: // Update message
postID := bf.ReadUint32()
bf.ReadBytes(8)
titleLength := bf.ReadUint32()
bodyLength := bf.ReadUint32()
title := stringsupport.SJISToUTF8(bf.ReadBytes(uint(titleLength)))
body := stringsupport.SJISToUTF8(bf.ReadBytes(uint(bodyLength)))
s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", title, body, postID)
s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", pkt.Title, pkt.Body, pkt.PostID)
case 3: // Update stamp
postID := bf.ReadUint32()
bf.ReadBytes(8)
stampID := bf.ReadUint32()
s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", stampID, postID)
s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", pkt.StampID, pkt.PostID)
case 4: // Like message
postID := bf.ReadUint32()
bf.ReadBytes(8)
likeState := bf.ReadBool()
var likedBy string
err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", postID).Scan(&likedBy)
err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", pkt.PostID).Scan(&likedBy)
if err != nil {
s.logger.Error("Failed to get guild message like data from db", zap.Error(err))
} else {
if likeState {
if pkt.LikeState {
likedBy = stringsupport.CSVAdd(likedBy, int(s.charID))
s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID)
s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, pkt.PostID)
} else {
likedBy = stringsupport.CSVRemove(likedBy, int(s.charID))
s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID)
s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, pkt.PostID)
}
}
case 5: // Check for new messages
@@ -1937,7 +1923,33 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfEntryRookieGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryRookieGuild)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
var guilds []*Guild
var guildID uint32
rows, err := s.server.db.Queryx(fmt.Sprintf(`%s WHERE return_type = 2`, guildInfoSelectQuery))
if err == nil {
for rows.Next() {
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
guilds = append(guilds, guild)
}
}
for i := range guilds {
if guilds[i].MemberCount < 60 {
guildID = guilds[i].ID
break
}
}
if guildID == 0 {
guildID, _ = CreateGuild(s, fmt.Sprintf(s.server.dict["returnGuild"], len(guilds)+1))
s.server.db.Exec(`UPDATE guilds SET is_return = 2, rank_rp = 1200 WHERE id = $1`, guildID)
} else {
s.server.db.Exec(`
INSERT INTO guild_characters (guild_id, character_id, order_index)
VALUES ($1, $2, (SELECT MAX(order_index) + 1 FROM guild_characters WHERE guild_id = $1))
`, guildID, s.charID)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(guildID)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateForceGuildRank(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -51,6 +51,8 @@ func getLangStrings(s *Server) map[string]string {
strings["guildInviteDeclinedName"] = "辞退しました"
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
strings["returnGuild"] = "復帰猟団%d"
default:
strings["language"] = "English"
strings["cafeReset"] = "Resets on %d/%d"
@@ -99,6 +101,8 @@ func getLangStrings(s *Server) map[string]string {
strings["guildInviteDeclinedName"] = "Declined"
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
strings["returnGuild"] = "Return Clan %d"
}
return strings
}