Merge branch 'main' of https://github.com/ZeruLight/Erupe into feature/enum-event

This commit is contained in:
stratic-dev
2025-04-30 15:59:23 +00:00
40 changed files with 569 additions and 211 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
bin/

View File

@@ -10,16 +10,17 @@ on:
- 'go.mod' - 'go.mod'
- 'go.sum' - 'go.sum'
- 'main.go' - 'main.go'
- '.github/workflows/go.yml'
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.21'
@@ -27,7 +28,7 @@ jobs:
run: env GOOS=linux GOARCH=amd64 go build -v run: env GOOS=linux GOARCH=amd64 go build -v
- name: Upload Linux-amd64 artifacts - name: Upload Linux-amd64 artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Linux-amd64 name: Linux-amd64
path: | path: |
@@ -42,7 +43,7 @@ jobs:
run: env GOOS=windows GOARCH=amd64 go build -v run: env GOOS=windows GOARCH=amd64 go build -v
- name: Upload Windows-amd64 artifacts - name: Upload Windows-amd64 artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Windows-amd64 name: Windows-amd64
path: | path: |

View File

@@ -29,6 +29,23 @@ func SJISToUTF8(b []byte) string {
return string(result) return string(result)
} }
func ToNGWord(x string) []uint16 {
var w []uint16
for _, r := range []rune(x) {
if r > 0xFF {
t := UTF8ToSJIS(string(r))
if len(t) > 1 {
w = append(w, uint16(t[1])<<8|uint16(t[0]))
} else {
w = append(w, uint16(t[0]))
}
} else {
w = append(w, uint16(r))
}
}
return w
}
func PaddedString(x string, size uint, t bool) []byte { func PaddedString(x string, size uint, t bool) []byte {
if t { if t {
e := japanese.ShiftJIS.NewEncoder() e := japanese.ShiftJIS.NewEncoder()

View File

@@ -21,6 +21,7 @@
"QuestCacheExpiry": 300, "QuestCacheExpiry": 300,
"CommandPrefix": "!", "CommandPrefix": "!",
"AutoCreateAccount": true, "AutoCreateAccount": true,
"LoopDelay": 50,
"DefaultCourses": [1, 23, 24], "DefaultCourses": [1, 23, 24],
"EarthStatus": 0, "EarthStatus": 0,
"EarthID": 0, "EarthID": 0,
@@ -176,6 +177,11 @@
"Enabled": true, "Enabled": true,
"Description": "Toggle the Quest timer", "Description": "Toggle the Quest timer",
"Prefix": "timer" "Prefix": "timer"
}, {
"Name": "Playtime",
"Enabled": true,
"Description": "Show your playtime",
"Prefix": "playtime"
} }
], ],
"Courses": [ "Courses": [

View File

@@ -81,6 +81,7 @@ type Config struct {
QuestCacheExpiry int // Number of seconds to keep quest data cached QuestCacheExpiry int // Number of seconds to keep quest data cached
CommandPrefix string // The prefix for commands CommandPrefix string // The prefix for commands
AutoCreateAccount bool // Automatically create accounts if they don't exist AutoCreateAccount bool // Automatically create accounts if they don't exist
LoopDelay int // Delay in milliseconds between each loop iteration
DefaultCourses []uint16 DefaultCourses []uint16
EarthStatus int32 EarthStatus int32
EarthID int32 EarthID int32

View File

@@ -64,3 +64,7 @@ if you want all the logs and you want it to be in an attached state
```bash ```bash
docker-compose up docker-compose up
``` ```
# Troubleshooting
Q: My Postgres will not populate. A: You're setup.sh is maybe saved as CRLF it needs to be saved as LF.

BIN
erupe.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 88 KiB

8
go.mod
View File

@@ -10,9 +10,9 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/spf13/viper v1.17.0 github.com/spf13/viper v1.17.0
go.uber.org/zap v1.26.0 go.uber.org/zap v1.26.0
golang.org/x/crypto v0.17.0 golang.org/x/crypto v0.31.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/text v0.14.0 golang.org/x/text v0.21.0
) )
require ( require (
@@ -31,8 +31,8 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.28.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

16
go.sum
View File

@@ -220,8 +220,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -289,8 +289,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -345,8 +345,8 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -356,8 +356,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -31,7 +31,7 @@ func (m *MsgMhfAcquireCafeItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl
m.ItemType = bf.ReadUint16() m.ItemType = bf.ReadUint16()
m.ItemID = bf.ReadUint16() m.ItemID = bf.ReadUint16()
m.Quant = bf.ReadUint16() m.Quant = bf.ReadUint16()
if _config.ErupeConfig.RealClientMode >= _config.G1 { if _config.ErupeConfig.RealClientMode >= _config.G6 {
m.PointCost = bf.ReadUint32() m.PointCost = bf.ReadUint32()
} else { } else {
m.PointCost = uint32(bf.ReadUint16()) m.PointCost = uint32(bf.ReadUint16())

View File

@@ -37,13 +37,16 @@ func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli
} }
m.Stamps = bf.ReadUint16() m.Stamps = bf.ReadUint16()
bf.ReadUint16() // Zeroed bf.ReadUint16() // Zeroed
if _config.ErupeConfig.RealClientMode > _config.Z1 { if _config.ErupeConfig.RealClientMode >= _config.Z2 {
m.Reward1 = uint16(bf.ReadUint32()) m.Reward1 = uint16(bf.ReadUint32())
m.Reward2 = uint16(bf.ReadUint32()) m.Reward2 = uint16(bf.ReadUint32())
m.Item1 = uint16(bf.ReadUint32()) m.Item1 = uint16(bf.ReadUint32())
m.Item2 = uint16(bf.ReadUint32()) m.Item2 = uint16(bf.ReadUint32())
m.Quantity1 = uint16(bf.ReadUint32()) m.Quantity1 = uint16(bf.ReadUint32())
m.Quantity2 = uint16(bf.ReadUint32()) m.Quantity2 = uint16(bf.ReadUint32())
} else {
m.Reward1 = 10
m.Reward2 = 10
} }
return nil return nil
} }

View File

@@ -40,8 +40,8 @@ func (m *MsgSysTerminalLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
entryCount := int(bf.ReadUint16()) entryCount := int(bf.ReadUint16())
bf.ReadUint16() // Zeroed bf.ReadUint16() // Zeroed
var e TerminalLogEntry
for i := 0; i < entryCount; i++ { for i := 0; i < entryCount; i++ {
var e TerminalLogEntry
e.Index = bf.ReadUint32() e.Index = bf.ReadUint32()
e.Type1 = bf.ReadUint8() e.Type1 = bf.ReadUint8()
e.Type2 = bf.ReadUint8() e.Type2 = bf.ReadUint8()

Binary file not shown.

View File

@@ -1,13 +1,15 @@
BEGIN; BEGIN;
TRUNCATE public.cafebonus;
INSERT INTO public.cafebonus (time_req, item_type, item_id, quantity) INSERT INTO public.cafebonus (time_req, item_type, item_id, quantity)
VALUES VALUES
(1800, 17, 0, 250), (1800, 17, 0, 50),
(3600, 17, 0, 500), (3600, 17, 0, 100),
(7200, 17, 0, 1000), (7200, 17, 0, 200),
(10800, 17, 0, 1500), (10800, 17, 0, 300),
(18000, 17, 0, 1750), (18000, 17, 0, 350),
(28800, 17, 0, 3000), (28800, 17, 0, 500),
(43200, 17, 0, 4000); (43200, 17, 0, 500);
END; END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE distribution ADD COLUMN rights INTEGER;
ALTER TABLE distribution ADD COLUMN selection BOOLEAN;
END;

View File

@@ -3,4 +3,4 @@ BEGIN;
ALTER TABLE IF EXISTS public.stamps RENAME hl_next TO hl_checked; ALTER TABLE IF EXISTS public.stamps RENAME hl_next TO hl_checked;
ALTER TABLE IF EXISTS public.stamps RENAME ex_next TO ex_checked; ALTER TABLE IF EXISTS public.stamps RENAME ex_next TO ex_checked;
END; END;

View File

@@ -0,0 +1,5 @@
BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE mail ADD COLUMN IF NOT EXISTS is_sys_message BOOLEAN NOT NULL DEFAULT false;
END;

View File

@@ -88,7 +88,7 @@ func updateRights(s *Session) {
Rights: s.courses, Rights: s.courses,
UnkSize: 0, UnkSize: 0,
} }
s.QueueSendMHF(update) s.QueueSendMHFNonBlocking(update)
} }
func handleMsgHead(s *Session, p mhfpacket.MHFPacket) {} func handleMsgHead(s *Session, p mhfpacket.MHFPacket) {}
@@ -143,7 +143,6 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.token = pkt.LoginTokenString s.token = pkt.LoginTokenString
s.Unlock() s.Unlock()
updateRights(s)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp
@@ -193,7 +192,7 @@ func logoutPlayer(s *Session) {
for _, sess := range s.server.sessions { for _, sess := range s.server.sessions {
for rSlot := range stage.reservedClientSlots { for rSlot := range stage.reservedClientSlots {
if sess.charID == rSlot && sess.stage != nil && sess.stage.id[3:5] != "Qs" { if sess.charID == rSlot && sess.stage != nil && sess.stage.id[3:5] != "Qs" {
sess.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) sess.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{})
} }
} }
} }
@@ -1051,32 +1050,58 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStampcardStamp) pkt := p.(*mhfpacket.MsgMhfStampcardStamp)
rewards := []struct {
HR uint16
Item1 uint16
Quantity1 uint16
Item2 uint16
Quantity2 uint16
}{
{0, 6164, 1, 6164, 2},
{50, 6164, 2, 6164, 3},
{100, 6164, 3, 5392, 1},
{300, 5392, 1, 5392, 3},
{999, 5392, 1, 5392, 4},
}
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
for _, reward := range rewards {
if pkt.HR >= reward.HR {
pkt.Item1 = reward.Item1
pkt.Quantity1 = reward.Quantity1
pkt.Item2 = reward.Item2
pkt.Quantity2 = reward.Quantity2
}
}
}
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(pkt.HR) bf.WriteUint16(pkt.HR)
var stamps uint16
_ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps)
if _config.ErupeConfig.RealClientMode >= _config.G1 { if _config.ErupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint16(pkt.GR) bf.WriteUint16(pkt.GR)
} }
var stamps, rewardTier, rewardUnk uint16
reward := mhfitem.MHFItemStack{Item: mhfitem.MHFItem{}}
s.server.db.QueryRow(`UPDATE characters SET stampcard = stampcard + $1 WHERE id = $2 RETURNING stampcard`, pkt.Stamps, s.charID).Scan(&stamps)
bf.WriteUint16(stamps - pkt.Stamps)
bf.WriteUint16(stamps) bf.WriteUint16(stamps)
stamps += pkt.Stamps
bf.WriteUint16(stamps) if stamps/30 > (stamps-pkt.Stamps)/30 {
s.server.db.Exec(`UPDATE characters SET stampcard = $1 WHERE id = $2`, stamps, s.charID) rewardTier = 2
if stamps%30 == 0 { rewardUnk = pkt.Reward2
bf.WriteUint16(2) reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2}
bf.WriteUint16(pkt.Reward2) addWarehouseItem(s, reward)
bf.WriteUint16(pkt.Item2) } else if stamps/15 > (stamps-pkt.Stamps)/15 {
bf.WriteUint16(pkt.Quantity2) rewardTier = 1
addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2}) rewardUnk = pkt.Reward1
} else if stamps%15 == 0 { reward = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1}
bf.WriteUint16(1) addWarehouseItem(s, reward)
bf.WriteUint16(pkt.Reward1)
bf.WriteUint16(pkt.Item1)
bf.WriteUint16(pkt.Quantity1)
addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1})
} else {
bf.WriteBytes(make([]byte, 8))
} }
bf.WriteUint16(rewardTier)
bf.WriteUint16(rewardUnk)
bf.WriteUint16(reward.Item.ItemID)
bf.WriteUint16(reward.Quantity)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse" "erupe-ce/common/mhfcourse"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt" "fmt"
"go.uber.org/zap" "go.uber.org/zap"
@@ -92,10 +93,11 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
if mhfcourse.CourseExists(30, s.courses) { if mhfcourse.CourseExists(30, s.courses) {
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
} }
bf.WriteUint32(cafeTime) // Total cafe time bf.WriteUint32(cafeTime)
bf.WriteUint16(0) if _config.ErupeConfig.RealClientMode >= _config.ZZ {
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true) bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -164,7 +166,7 @@ func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
FROM characters ch FROM characters ch
WHERE ch.id = $1 WHERE ch.id = $1
) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart) ) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart)
if err != nil { if err != nil || !mhfcourse.CourseExists(30, s.courses) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else { } else {
for rows.Next() { for rows.Next() {

View File

@@ -82,7 +82,7 @@ func sendServerChatMessage(s *Session, message string) {
RawDataPayload: bf.Data(), RawDataPayload: bf.Data(),
} }
s.QueueSendMHF(castedBin) s.QueueSendMHFNonBlocking(castedBin)
} }
func parseChatCommand(s *Session, command string) { func parseChatCommand(s *Session, command string) {
@@ -198,7 +198,7 @@ func parseChatCommand(s *Session, command string) {
temp.Build(deleteNotif, s.clientContext) temp.Build(deleteNotif, s.clientContext)
} }
deleteNotif.WriteUint16(uint16(network.MSG_SYS_END)) deleteNotif.WriteUint16(uint16(network.MSG_SYS_END))
s.QueueSend(deleteNotif.Data()) s.QueueSendNonBlocking(deleteNotif.Data())
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
reloadNotif := byteframe.NewByteFrame() reloadNotif := byteframe.NewByteFrame()
for _, session := range s.server.sessions { for _, session := range s.server.sessions {
@@ -233,7 +233,7 @@ func parseChatCommand(s *Session, command string) {
temp.Build(reloadNotif, s.clientContext) temp.Build(reloadNotif, s.clientContext)
} }
reloadNotif.WriteUint16(uint16(network.MSG_SYS_END)) reloadNotif.WriteUint16(uint16(network.MSG_SYS_END))
s.QueueSend(reloadNotif.Data()) s.QueueSendNonBlocking(reloadNotif.Data())
} else { } else {
sendDisabledCommandMessage(s, commands["Reload"]) sendDisabledCommandMessage(s, commands["Reload"])
} }
@@ -381,7 +381,7 @@ func parseChatCommand(s *Session, command string) {
payload.WriteInt16(int16(x)) // X payload.WriteInt16(int16(x)) // X
payload.WriteInt16(int16(y)) // Y payload.WriteInt16(int16(y)) // Y
payloadBytes := payload.Data() payloadBytes := payload.Data()
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{ s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCastedBinary{
CharID: s.charID, CharID: s.charID,
MessageType: BinaryMessageTypeState, MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes, RawDataPayload: payloadBytes,
@@ -407,6 +407,13 @@ func parseChatCommand(s *Session, command string) {
} else { } else {
sendDisabledCommandMessage(s, commands["Discord"]) sendDisabledCommandMessage(s, commands["Discord"])
} }
case commands["Playtime"].Prefix:
if commands["Playtime"].Enabled || s.isOp() {
playtime := s.playtime + uint32(time.Now().Sub(s.playtimeTime).Seconds())
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.playtime, playtime/60/60, playtime/60%60, playtime%60))
} else {
sendDisabledCommandMessage(s, commands["Playtime"])
}
case commands["Help"].Prefix: case commands["Help"].Prefix:
if commands["Help"].Enabled || s.isOp() { if commands["Help"].Enabled || s.isOp() {
for _, command := range commands { for _, command := range commands {
@@ -532,7 +539,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
char := s.server.FindSessionByCharID(targetID) char := s.server.FindSessionByCharID(targetID)
if char != nil { if char != nil {
char.QueueSendMHF(resp) char.QueueSendMHFNonBlocking(resp)
} }
} }
default: default:

View File

@@ -23,6 +23,7 @@ const (
pGalleryData // +1748 pGalleryData // +1748
pToreData // +240 pToreData // +240
pGardenData // +68 pGardenData // +68
pPlaytime // +4
pWeaponType // +1 pWeaponType // +1
pWeaponID // +2 pWeaponID // +2
pHR // +2 pHR // +2
@@ -45,6 +46,7 @@ type CharacterSaveData struct {
GalleryData []byte GalleryData []byte
ToreData []byte ToreData []byte
GardenData []byte GardenData []byte
Playtime uint32
WeaponType uint8 WeaponType uint8
WeaponID uint16 WeaponID uint16
HR uint16 HR uint16
@@ -59,6 +61,7 @@ func getPointers() map[SavePointer]int {
pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576} pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576}
switch _config.ErupeConfig.RealClientMode { switch _config.ErupeConfig.RealClientMode {
case _config.ZZ: case _config.ZZ:
pointers[pPlaytime] = 128356
pointers[pWeaponID] = 128522 pointers[pWeaponID] = 128522
pointers[pWeaponType] = 128789 pointers[pWeaponType] = 128789
pointers[pHouseTier] = 129900 pointers[pHouseTier] = 129900
@@ -74,6 +77,7 @@ func getPointers() map[SavePointer]int {
case _config.Z2, _config.Z1, _config.G101, _config.G10, _config.G91, _config.G9, _config.G81, _config.G8, case _config.Z2, _config.Z1, _config.G101, _config.G10, _config.G91, _config.G9, _config.G81, _config.G8,
_config.G7, _config.G61, _config.G6, _config.G52, _config.G51, _config.G5, _config.GG, _config.G32, _config.G31, _config.G7, _config.G61, _config.G6, _config.G52, _config.G51, _config.G5, _config.GG, _config.G32, _config.G31,
_config.G3, _config.G2, _config.G1: _config.G3, _config.G2, _config.G1:
pointers[pPlaytime] = 92356
pointers[pWeaponID] = 92522 pointers[pWeaponID] = 92522
pointers[pWeaponType] = 92789 pointers[pWeaponType] = 92789
pointers[pHouseTier] = 93900 pointers[pHouseTier] = 93900
@@ -87,6 +91,7 @@ func getPointers() map[SavePointer]int {
pointers[pRP] = 106614 pointers[pRP] = 106614
pointers[pKQF] = 110720 pointers[pKQF] = 110720
case _config.F5, _config.F4: case _config.F5, _config.F4:
pointers[pPlaytime] = 60356
pointers[pWeaponID] = 60522 pointers[pWeaponID] = 60522
pointers[pWeaponType] = 60789 pointers[pWeaponType] = 60789
pointers[pHouseTier] = 61900 pointers[pHouseTier] = 61900
@@ -98,6 +103,7 @@ func getPointers() map[SavePointer]int {
pointers[pGardenData] = 74424 pointers[pGardenData] = 74424
pointers[pRP] = 74614 pointers[pRP] = 74614
case _config.S6: case _config.S6:
pointers[pPlaytime] = 12356
pointers[pWeaponID] = 12522 pointers[pWeaponID] = 12522
pointers[pWeaponType] = 12789 pointers[pWeaponType] = 12789
pointers[pHouseTier] = 13900 pointers[pHouseTier] = 13900
@@ -231,6 +237,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748] save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748]
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240] save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240]
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68] save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
save.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+4])
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2]) save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2])

View File

@@ -54,6 +54,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
} }
characterSaveData.updateStructWithSaveData() characterSaveData.updateStructWithSaveData()
s.playtime = characterSaveData.Playtime
s.playtimeTime = time.Now()
// Bypass name-checker if new // Bypass name-checker if new
if characterSaveData.IsNewCharacter == true { if characterSaveData.IsNewCharacter == true {
s.Name = characterSaveData.Name s.Name = characterSaveData.Name

View File

@@ -13,6 +13,7 @@ import (
type Distribution struct { type Distribution struct {
ID uint32 `db:"id"` ID uint32 `db:"id"`
Deadline time.Time `db:"deadline"` Deadline time.Time `db:"deadline"`
Rights uint32 `db:"rights"`
TimesAcceptable uint16 `db:"times_acceptable"` TimesAcceptable uint16 `db:"times_acceptable"`
TimesAccepted uint16 `db:"times_accepted"` TimesAccepted uint16 `db:"times_accepted"`
MinHR int16 `db:"min_hr"` MinHR int16 `db:"min_hr"`
@@ -23,7 +24,7 @@ type Distribution struct {
MaxGR int16 `db:"max_gr"` MaxGR int16 `db:"max_gr"`
EventName string `db:"event_name"` EventName string `db:"event_name"`
Description string `db:"description"` Description string `db:"description"`
Data []byte `db:"data"` Selection bool `db:"selection"`
} }
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
@@ -32,7 +33,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
var itemDists []Distribution var itemDists []Distribution
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
rows, err := s.server.db.Queryx(` rows, err := s.server.db.Queryx(`
SELECT d.id, event_name, description, times_acceptable, SELECT d.id, event_name, description, COALESCE(rights, 0) AS rights, COALESCE(selection, false) AS selection, times_acceptable,
COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr, COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr,
COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr, COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr,
COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr, COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr,
@@ -60,7 +61,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
for _, dist := range itemDists { for _, dist := range itemDists {
bf.WriteUint32(dist.ID) bf.WriteUint32(dist.ID)
bf.WriteUint32(uint32(dist.Deadline.Unix())) bf.WriteUint32(uint32(dist.Deadline.Unix()))
bf.WriteUint32(0) // Unk bf.WriteUint32(dist.Rights)
bf.WriteUint16(dist.TimesAcceptable) bf.WriteUint16(dist.TimesAcceptable)
bf.WriteUint16(dist.TimesAccepted) bf.WriteUint16(dist.TimesAccepted)
if _config.ErupeConfig.RealClientMode >= _config.G9 { if _config.ErupeConfig.RealClientMode >= _config.G9 {
@@ -79,7 +80,11 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(0) // Unk bf.WriteUint16(0) // Unk
} }
if _config.ErupeConfig.RealClientMode >= _config.G8 { if _config.ErupeConfig.RealClientMode >= _config.G8 {
bf.WriteUint8(0) // Unk if dist.Selection {
bf.WriteUint8(2) // Selection
} else {
bf.WriteUint8(0)
}
} }
if _config.ErupeConfig.RealClientMode >= _config.G7 { if _config.ErupeConfig.RealClientMode >= _config.G7 {
bf.WriteUint16(0) // Unk bf.WriteUint16(0) // Unk

View File

@@ -292,7 +292,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
} else { } else {
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP) bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP)
} }
bf.WriteUint16(500) bf.WriteUint16(100) // Reward multiplier (%)
var temp uint32 var temp uint32
bf.WriteUint16(4) bf.WriteUint16(4)

View File

@@ -971,14 +971,21 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(0) bf.WriteUint8(0)
bf.WriteUint8(0) bf.WriteUint8(0)
bf.WriteBool(!guild.Recruiting) flags := uint8(0)
if !guild.Recruiting {
flags |= 0x01
}
//if guild.Suspended {
// flags |= 0x02
//}
bf.WriteUint8(flags)
if characterGuildData == nil || characterGuildData.IsApplicant { if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00) bf.WriteUint16(0)
} else if guild.LeaderCharID == s.charID { } else if guild.LeaderCharID == s.charID {
bf.WriteUint16(0x01) bf.WriteUint16(1)
} else { } else {
bf.WriteUint16(0x02) bf.WriteUint16(2)
} }
bf.WriteUint32(uint32(guild.CreatedAt.Unix())) bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
@@ -1098,20 +1105,25 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(applicant.CharID) bf.WriteUint32(applicant.CharID)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint16(applicant.HR) bf.WriteUint16(applicant.HR)
bf.WriteUint16(applicant.GR) if s.server.erupeConfig.RealClientMode >= _config.G10 {
bf.WriteUint16(applicant.GR)
}
ps.Uint8(bf, applicant.Name, true) ps.Uint8(bf, applicant.Name, true)
} }
} }
type UnkGuildInfo struct { type Activity struct {
Unk0 uint8 Pass uint8
Unk1 uint8 Unk1 uint8
Unk2 uint8 Unk2 uint8
} }
unkGuildInfo := []UnkGuildInfo{} activity := []Activity{
bf.WriteUint8(uint8(len(unkGuildInfo))) // 1,0,0 = ok
for _, info := range unkGuildInfo { // 0,0,0 = ng
bf.WriteUint8(info.Unk0) }
bf.WriteUint8(uint8(len(activity)))
for _, info := range activity {
bf.WriteUint8(info.Pass)
bf.WriteUint8(info.Unk1) bf.WriteUint8(info.Unk1)
bf.WriteUint8(info.Unk2) bf.WriteUint8(info.Unk2)
} }
@@ -1159,7 +1171,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else { } else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
} }
@@ -1526,7 +1538,7 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight) pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight)
guild, err := GetGuildInfoByCharacterId(s, s.charID) guild, err := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil && s.prevGuildID != 0 { if guild == nil || s.prevGuildID != 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID) guild, err = GetGuildInfoByID(s, s.prevGuildID)
s.prevGuildID = 0 s.prevGuildID = 0
if guild == nil || err != nil { if guild == nil || err != nil {

View File

@@ -61,35 +61,41 @@ func (gm *GuildMember) Save(s *Session) error {
} }
const guildMembersSelectSQL = ` const guildMembersSelectSQL = `
SELECT * FROM ( SELECT
SELECT COALESCE(g.id, 0) AS guild_id,
g.id AS guild_id, joined_at,
joined_at, COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls, COALESCE(rp_today, 0) AS rp_today,
COALESCE(rp_today, 0) AS rp_today, COALESCE(rp_yesterday, 0) AS rp_yesterday,
COALESCE(rp_yesterday, 0) AS rp_yesterday, c.name,
c.name, c.id AS character_id,
c.id AS character_id, COALESCE(order_index, 0) AS order_index,
COALESCE(order_index, 0) AS order_index, c.last_login,
c.last_login, COALESCE(recruiter, false) AS recruiter,
COALESCE(recruiter, false) AS recruiter, COALESCE(avoid_leadership, false) AS avoid_leadership,
COALESCE(avoid_leadership, false) AS avoid_leadership, c.hr,
c.hr, c.gr,
c.gr, c.weapon_id,
c.weapon_id, c.weapon_type,
c.weapon_type, CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader,
EXISTS(SELECT 1 FROM guild_applications ga WHERE ga.character_id=c.id AND application_type='applied') AS is_applicant, character.is_applicant
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader FROM (
SELECT character_id, true as is_applicant, guild_id
FROM guild_applications ga
WHERE ga.application_type = 'applied'
UNION
SELECT character_id, false as is_applicant, guild_id
FROM guild_characters gc FROM guild_characters gc
LEFT JOIN characters c ON c.id = gc.character_id ) character
LEFT JOIN guilds g ON g.id = gc.guild_id JOIN characters c on character.character_id = c.id
) AS subquery LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
LEFT JOIN guilds g ON g.id = gc.guild_id
` `
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) { func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(` rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s %s
WHERE guild_id = $1 AND is_applicant = $2 WHERE character.guild_id = $1 AND is_applicant = $2
`, guildMembersSelectSQL), guildID, applicants) `, guildMembersSelectSQL), guildID, applicants)
if err != nil { if err != nil {
@@ -115,7 +121,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe
} }
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) { func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID) rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
if err != nil { if err != nil {
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID)) s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))

View File

@@ -367,13 +367,17 @@ func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) { func initializeWarehouse(s *Session) {
pkt := p.(*mhfpacket.MsgMhfOperateWarehouse)
var t int var t int
err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t) err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t)
if err != nil { if err != nil {
s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID) s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID)
} }
}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateWarehouse)
initializeWarehouse(s)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint8(pkt.Operation) bf.WriteUint8(pkt.Operation)
switch pkt.Operation { switch pkt.Operation {
@@ -446,6 +450,7 @@ func addWarehouseEquipment(s *Session, equipment mhfitem.MHFEquipment) {
} }
func warehouseGetItems(s *Session, index uint8) []mhfitem.MHFItemStack { func warehouseGetItems(s *Session, index uint8) []mhfitem.MHFItemStack {
initializeWarehouse(s)
var data []byte var data []byte
var items []mhfitem.MHFItemStack var items []mhfitem.MHFItemStack
s.server.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data) s.server.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data)

View File

@@ -185,7 +185,7 @@ func SendMailNotification(s *Session, m *Mail, recipient *Session) {
castedBinary.Build(bf, s.clientContext) castedBinary.Build(bf, s.clientContext)
recipient.QueueSendMHF(castedBinary) recipient.QueueSendMHFNonBlocking(castedBinary)
} }
func getCharacterName(s *Session, charID uint32) string { func getCharacterName(s *Session, charID uint32) string {

View File

@@ -21,7 +21,6 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
data = make([]byte, 9) data = make([]byte, 9)
} }
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
@@ -157,60 +156,60 @@ func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW) pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
if pkt.Op > 0 { bf := byteframe.NewByteFrame()
bf := byteframe.NewByteFrame()
var pactID uint32
var name string
var cid uint32
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID) var pactID, cid uint32
if pactID > 0 { var name string
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid) s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
bf.WriteUint8(1) // numLends if pactID > 0 {
bf.WriteUint32(pactID) s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
bf.WriteUint32(cid) bf.WriteUint8(1) // numLends
bf.WriteBool(false) // ? bf.WriteUint32(pactID)
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix())) bf.WriteUint32(cid)
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix())) bf.WriteBool(true) // Escort enabled
bf.WriteBytes(stringsupport.PaddedString(name, 18, true)) bf.WriteUint32(uint32(TimeAdjusted().Unix()))
} else { bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
bf.WriteUint8(0) bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
}
if pkt.Op < 2 {
var loans uint8
temp := byteframe.NewByteFrame()
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
for rows.Next() {
loans++
rows.Scan(&name, &cid, &pactID)
temp.WriteUint32(pactID)
temp.WriteUint32(cid)
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
}
bf.WriteUint8(loans)
bf.WriteBytes(temp.Data())
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
var data []byte
var gcp uint32
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
resp := byteframe.NewByteFrame()
resp.WriteUint16(0)
if len(data) == 0 {
resp.WriteBool(false)
} else { } else {
resp.WriteBool(true) bf.WriteUint8(0)
resp.WriteBytes(data)
} }
resp.WriteUint32(gcp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) if pkt.Op != 2 && pkt.Op != 5 {
var loans uint8
temp := byteframe.NewByteFrame()
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
for rows.Next() {
err := rows.Scan(&name, &cid, &pactID)
if err != nil {
continue
}
loans++
temp.WriteUint32(pactID)
temp.WriteUint32(cid)
temp.WriteUint32(uint32(TimeAdjusted().Unix()))
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
}
bf.WriteUint8(loans)
bf.WriteBytes(temp.Data())
if pkt.Op != 1 && pkt.Op != 4 {
var data []byte
var gcp uint32
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
if len(data) == 0 {
bf.WriteBool(false)
} else {
bf.WriteBool(true)
bf.WriteBytes(data)
}
bf.WriteUint32(gcp)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -238,8 +238,10 @@ func loadQuestFile(s *Session, questId int) []byte {
} }
questBody.WriteBytes(newStrings.Data()) questBody.WriteBytes(newStrings.Data())
s.server.questCacheLock.Lock()
s.server.questCacheData[questId] = questBody.Data() s.server.questCacheData[questId] = questBody.Data()
s.server.questCacheTime[questId] = time.Now() s.server.questCacheTime[questId] = time.Now()
s.server.questCacheLock.Unlock()
return questBody.Data() return questBody.Data()
} }

View File

@@ -129,12 +129,12 @@ func (s *Session) notifyRavi() {
raviNotif.WriteUint16(0x0010) // End it. raviNotif.WriteUint16(0x0010) // End it.
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente { if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
for session := range sema.clients { for session := range sema.clients {
session.QueueSend(raviNotif.Data()) session.QueueSendNonBlocking(raviNotif.Data())
} }
} else { } else {
for session := range sema.clients { for session := range sema.clients {
if session.charID == s.charID { if session.charID == s.charID {
session.QueueSend(raviNotif.Data()) session.QueueSendNonBlocking(raviNotif.Data())
} }
} }
} }

View File

@@ -65,6 +65,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore) pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore)
SemaphoreID := pkt.SemaphoreID SemaphoreID := pkt.SemaphoreID
if s.server.HasSemaphore(s) {
s.semaphoreMode = !s.semaphoreMode
}
if s.semaphoreMode {
s.semaphoreID[1]++
} else {
s.semaphoreID[0]++
}
newSemaphore, exists := s.server.semaphore[SemaphoreID] newSemaphore, exists := s.server.semaphore[SemaphoreID]
if !exists { if !exists {
s.server.semaphoreLock.Lock() s.server.semaphoreLock.Lock()
@@ -77,7 +86,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
maxPlayers: 127, maxPlayers: 127,
} }
} else { } else {
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1) s.server.semaphore[SemaphoreID] = NewSemaphore(s, SemaphoreID, 1)
} }
newSemaphore = s.server.semaphore[SemaphoreID] newSemaphore = s.server.semaphore[SemaphoreID]
s.server.semaphoreLock.Unlock() s.server.semaphoreLock.Unlock()
@@ -103,7 +112,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysAcquireSemaphore) pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists { if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
sema.clients[s] = s.charID sema.host = s
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(sema.id) bf.WriteUint32(sema.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())

View File

@@ -59,7 +59,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.Unlock() s.Unlock()
// Tell the client to cleanup its current stage objects. // Tell the client to cleanup its current stage objects.
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{}) s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCleanupObject{})
// Confirm the stage entry. // Confirm the stage entry.
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
@@ -112,9 +112,8 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.stage.RUnlock() s.stage.RUnlock()
} }
newNotif.WriteUint16(0x0010) // End it.
if len(newNotif.Data()) > 2 { if len(newNotif.Data()) > 2 {
s.QueueSend(newNotif.Data()) s.QueueSendNonBlocking(newNotif.Data())
} }
} }
@@ -238,7 +237,7 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
for charID := range s.reservationStage.reservedClientSlots { for charID := range s.reservationStage.reservedClientSlots {
session := s.server.FindSessionByCharID(charID) session := s.server.FindSessionByCharID(charID)
if session != nil { if session != nil {
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{}) session.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{})
} }
} }
@@ -362,7 +361,7 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return return
} }
for { for i := 0; i < 10; i++ {
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage") s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
stage.Lock() stage.Lock()
stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]
@@ -370,13 +369,15 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage") s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
if gotBinary { if gotBinary {
doAckBufSucceed(s, pkt.AckHandle, stageBinary) doAckBufSucceed(s, pkt.AckHandle, stageBinary)
break return
} else { } else {
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1)) s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
continue continue
} }
} }
s.logger.Warn("MsgSysWaitStageBinary stage binary timeout")
doAckBufSucceed(s, pkt.AckHandle, []byte{})
} else { } else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID)) s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
} }

View File

@@ -75,6 +75,7 @@ type Server struct {
raviente *Raviente raviente *Raviente
questCacheLock sync.RWMutex
questCacheData map[int][]byte questCacheData map[int][]byte
questCacheTime map[int]time.Time questCacheTime map[int]time.Time
} }
@@ -207,6 +208,7 @@ func (s *Server) Start() error {
go s.acceptClients() go s.acceptClients()
go s.manageSessions() go s.manageSessions()
go s.invalidateSessions()
// Start the discord bot for chat integration. // Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil { if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
@@ -278,6 +280,21 @@ func (s *Server) manageSessions() {
} }
} }
func (s *Server) invalidateSessions() {
for {
if s.isShuttingDown {
break
}
for _, sess := range s.sessions {
if time.Now().Sub(sess.lastPacket) > time.Second*time.Duration(30) {
s.logger.Info("session timeout", zap.String("Name", sess.Name))
logoutPlayer(sess)
}
}
time.Sleep(time.Second * 10)
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions. // BroadcastMHF queues a MHFPacket to be sent to all sessions.
func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) { func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data. // Broadcast the data.
@@ -424,24 +441,13 @@ func (s *Server) FindObjectByChar(charID uint32) *Object {
return nil return nil
} }
func (s *Server) NextSemaphoreID() uint32 { func (s *Server) HasSemaphore(ses *Session) bool {
for { for _, semaphore := range s.semaphore {
exists := false if semaphore.host == ses {
s.semaphoreIndex = s.semaphoreIndex + 1 return true
if s.semaphoreIndex > 0xFFFF {
s.semaphoreIndex = 1
}
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true
break
}
}
if !exists {
break
} }
} }
return s.semaphoreIndex return false
} }
func (s *Server) Season() uint8 { func (s *Server) Season() uint8 {

View File

@@ -10,6 +10,7 @@ type i18n struct {
noOp string noOp string
disabled string disabled string
reload string reload string
playtime string
kqf struct { kqf struct {
get string get string
set struct { set struct {
@@ -175,6 +176,8 @@ func getLangStrings(s *Server) i18n {
i.commands.noOp = "You don't have permission to use this command" i.commands.noOp = "You don't have permission to use this command"
i.commands.disabled = "%s command is disabled" i.commands.disabled = "%s command is disabled"
i.commands.reload = "Reloading players..." i.commands.reload = "Reloading players..."
i.commands.playtime = "Playtime: %d hours %d minutes %d seconds"
i.commands.kqf.get = "KQF: %x" i.commands.kqf.get = "KQF: %x"
i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx" i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
i.commands.kqf.set.success = "KQF set, please switch Land/World" i.commands.kqf.set.success = "KQF set, please switch Land/World"

View File

@@ -22,15 +22,18 @@ type Semaphore struct {
// Max Players for Semaphore // Max Players for Semaphore
maxPlayers uint16 maxPlayers uint16
host *Session
} }
// NewSemaphore creates a new Semaphore with intialized values // NewSemaphore creates a new Semaphore with intialized values
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore { func NewSemaphore(s *Session, ID string, MaxPlayers uint16) *Semaphore {
sema := &Semaphore{ sema := &Semaphore{
name: ID, name: ID,
id: s.NextSemaphoreID(), id: s.GetSemaphoreID(),
clients: make(map[*Session]uint32), clients: make(map[*Session]uint32),
maxPlayers: MaxPlayers, maxPlayers: MaxPlayers,
host: s,
} }
return sema return sema
} }

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"erupe-ce/common/mhfcourse" "erupe-ce/common/mhfcourse"
_config "erupe-ce/config"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -15,6 +16,7 @@ import (
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -48,7 +50,12 @@ type Session struct {
kqf []byte kqf []byte
kqfOverride bool kqfOverride bool
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet. playtime uint32
playtimeTime time.Time
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
semaphoreMode bool
semaphoreID []uint16
// A stack containing the stage movement history (push on enter/move, pop on back) // A stack containing the stage movement history (push on enter/move, pop on back)
stageMoveStack *stringstack.StringStack stageMoveStack *stringstack.StringStack
@@ -79,6 +86,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
sessionStart: TimeAdjusted().Unix(), sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(), stageMoveStack: stringstack.New(),
ackStart: make(map[uint32]time.Time), ackStart: make(map[uint32]time.Time),
semaphoreID: make([]uint16, 2),
} }
s.SetObjectID() s.SetObjectID()
return s return s
@@ -96,22 +104,9 @@ func (s *Session) Start() {
// QueueSend queues a packet (raw []byte) to be sent. // QueueSend queues a packet (raw []byte) to be sent.
func (s *Session) QueueSend(data []byte) { func (s *Session) QueueSend(data []byte) {
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name) s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
select { err := s.cryptConn.SendPacket(append(data, []byte{0x00, 0x10}...))
case s.sendPackets <- packet{data, false}: if err != nil {
// Enqueued data s.logger.Warn("Failed to send packet")
default:
s.logger.Warn("Packet queue too full, flushing!")
var tempPackets []packet
for len(s.sendPackets) > 0 {
tempPacket := <-s.sendPackets
if !tempPacket.nonBlocking {
tempPackets = append(tempPackets, tempPacket)
}
}
for _, tempPacket := range tempPackets {
s.sendPackets <- tempPacket
}
s.sendPackets <- packet{data, false}
} }
} }
@@ -138,6 +133,19 @@ func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) {
s.QueueSend(bf.Data()) s.QueueSend(bf.Data())
} }
// QueueSendMHFNonBlocking queues a MHFPacket to be sent, dropping the packet entirely if the queue is full.
func (s *Session) QueueSendMHFNonBlocking(pkt mhfpacket.MHFPacket) {
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, s.clientContext)
// Queue it.
s.QueueSendNonBlocking(bf.Data())
}
// QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data. // QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data.
func (s *Session) QueueAck(ackHandle uint32, data []byte) { func (s *Session) QueueAck(ackHandle uint32, data []byte) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
@@ -150,26 +158,26 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) {
func (s *Session) sendLoop() { func (s *Session) sendLoop() {
var pkt packet var pkt packet
for { for {
var buf []byte
if s.closed { if s.closed {
return return
} }
for len(s.sendPackets) > 0 { for len(s.sendPackets) > 0 {
pkt = <-s.sendPackets pkt = <-s.sendPackets
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...)) buf = append(buf, pkt.data...)
}
if len(buf) > 0 {
err := s.cryptConn.SendPacket(append(buf, []byte{0x00, 0x10}...))
if err != nil { if err != nil {
s.logger.Warn("Failed to send packet") s.logger.Warn("Failed to send packet")
} }
} }
time.Sleep(5 * time.Millisecond) time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond)
} }
} }
func (s *Session) recvLoop() { func (s *Session) recvLoop() {
for { for {
if time.Now().Add(-30 * time.Second).After(s.lastPacket) {
logoutPlayer(s)
return
}
if s.closed { if s.closed {
logoutPlayer(s) logoutPlayer(s)
return return
@@ -185,7 +193,7 @@ func (s *Session) recvLoop() {
return return
} }
s.handlePacketGroup(pkt) s.handlePacketGroup(pkt)
time.Sleep(5 * time.Millisecond) time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond)
} }
} }
@@ -272,7 +280,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
} else { } else {
fmt.Printf("[%s] -> [%s]\n", sender, recipient) fmt.Printf("[%s] -> [%s]\n", sender, recipient)
} }
fmt.Printf("Opcode: %s\n", opcodePID) fmt.Printf("Opcode: (Dec: %d Hex: 0x%04X Name: %s) \n", opcode, opcode, opcodePID)
if s.server.erupeConfig.DebugOptions.LogMessageData { if s.server.erupeConfig.DebugOptions.LogMessageData {
if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength { if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
@@ -310,6 +318,14 @@ func (s *Session) NextObjectID() uint32 {
return bf.ReadUint32() return bf.ReadUint32()
} }
func (s *Session) GetSemaphoreID() uint32 {
if s.semaphoreMode {
return 0x000E0000 + uint32(s.semaphoreID[1])
} else {
return 0x000F0000 + uint32(s.semaphoreID[0])
}
}
func (s *Session) isOp() bool { func (s *Session) isOp() bool {
var op bool var op bool
err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op) err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op)

View File

@@ -86,7 +86,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
} }
} }
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
bf.WriteUint32(60) bf.WriteUint32(uint32(s.erupeConfig.GameplayOptions.ClanMemberLimits[len(s.erupeConfig.GameplayOptions.ClanMemberLimits)-1][1]))
return bf.Data() return bf.Data()
} }

View File

@@ -135,7 +135,203 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
bf.WriteUint32(s.server.getLastCID(uid)) bf.WriteUint32(s.server.getLastCID(uid))
bf.WriteUint32(s.server.getUserRights(uid)) bf.WriteUint32(s.server.getUserRights(uid))
ps.Uint16(bf, "", false) // filters
namNGWords := []string{}
msgNGWords := []string{}
filters := byteframe.NewByteFrame()
filters.SetLE()
filters.WriteNullTerminatedBytes([]byte("smc"))
smc := byteframe.NewByteFrame()
smc.SetLE()
smcData := []struct {
charGroup [][]rune
}{
{[][]rune{{'='}, {''}}},
{[][]rune{{')'}, {''}}},
{[][]rune{{'('}, {''}}},
{[][]rune{{'!'}, {''}}},
{[][]rune{{'/'}, {''}}},
{[][]rune{{'+'}, {''}}},
{[][]rune{{'&'}, {''}}},
{[][]rune{{'ぼ'}, {'ボ'}, {'ホ', '゙'}, {'ほ', '゙'}, {'ホ', '゙'}, {'ほ', '゛'}, {'ホ', '゛'}, {'ホ', '゛'}}},
{[][]rune{{'べ'}, {'ベ'}, {'ヘ', '゙'}, {'へ', '゙'}, {'ヘ', '゙'}, {'へ', '゛'}, {'ヘ', '゛'}, {'ヘ', '゛'}}},
{[][]rune{{'で'}, {'デ'}, {'テ', '゙'}, {'て', '゙'}, {'テ', '゙'}, {'て', '゛'}, {'テ', '゛'}, {'テ', '゛'}, {'〒', '゛'}, {'〒', '゙'}, {'乙', '゙'}, {'乙', '゛'}}},
{[][]rune{{'び'}, {'ビ'}, {'ヒ', '゙'}, {'ひ', '゙'}, {'ヒ', '゙'}, {'ひ', '゛'}, {'ヒ', '゛'}, {'ヒ', '゛'}}},
{[][]rune{{'ど'}, {'ド'}, {'ト', '゙'}, {'と', '゙'}, {'ト', '゙'}, {'と', '゛'}, {'ト', '゛'}, {'ト', '゛'}, {'┣', '゙'}, {'┣', '゛'}, {'├', '゙'}, {'├', '゛'}}},
{[][]rune{{'ば'}, {'バ'}, {'ハ', '゙'}, {'は', '゙'}, {'ハ', '゙'}, {'八', '゙'}, {'は', '゛'}, {'ハ', '゛'}, {'ハ', '゛'}, {'八', '゛'}}},
{[][]rune{{'つ', '゙'}, {'ヅ'}, {'ツ', '゙'}, {'つ', '゛'}, {'ツ', '゛'}, {'ツ', '゙'}, {'ツ', '゛'}, {'づ'}, {'っ', '゙'}, {'ッ', '゙'}, {'ッ', '゙'}, {'っ', '゛'}, {'ッ', '゛'}, {'ッ', '゛'}}},
{[][]rune{{'ぶ'}, {'ブ'}, {'フ', '゙'}, {'ヴ'}, {'ウ', '゙'}, {'う', '゛'}, {'う', '゙'}, {'ウ', '゙'}, {'ゥ', '゙'}, {'ぅ', '゙'}, {'ふ', '゙'}, {'フ', '゙'}, {'フ', '゛'}}},
{[][]rune{{'ぢ'}, {'ヂ'}, {'チ', '゙'}, {'ち', '゙'}, {'チ', '゙'}, {'ち', '゛'}, {'チ', '゛'}, {'チ', '゛'}, {'千', '゛'}, {'千', '゙'}}},
{[][]rune{{'だ'}, {'ダ'}, {'タ', '゙'}, {'た', '゙'}, {'タ', '゙'}, {'夕', '゙'}, {'た', '゛'}, {'タ', '゛'}, {'タ', '゛'}, {'夕', '゛'}}},
{[][]rune{{'ぞ'}, {'ゾ'}, {'ソ', '゙'}, {'そ', '゙'}, {'ソ', '゙'}, {'そ', '゛'}, {'ソ', '゛'}, {'ソ', '゛'}, {'ン', '゙'}, {'ン', '゛'}, {'ン', '゛'}, {'ン', '゙'}, {'リ', '゙'}, {'リ', '゙'}, {'リ', '゛'}, {'リ', '゛'}}},
{[][]rune{{'ぜ'}, {'セ', '゙'}, {'せ', '゙'}, {'セ', '゙'}, {'せ', '゛'}, {'セ', '゛'}, {'セ', '゛'}, {'ゼ'}}},
{[][]rune{{'ず'}, {'ズ'}, {'ス', '゙'}, {'す', '゙'}, {'ス', '゙'}, {'す', '゛'}, {'ス', '゛'}, {'ス', '゛'}}},
{[][]rune{{'じ'}, {'ジ'}, {'シ', '゙'}, {'し', '゙'}, {'シ', '゙'}, {'し', '゛'}, {'シ', '゛'}, {'シ', '゛'}}},
{[][]rune{{'ざ'}, {'ザ'}, {'サ', '゙'}, {'さ', '゙'}, {'サ', '゙'}, {'さ', '゛'}, {'サ', '゛'}, {'サ', '゛'}}},
{[][]rune{{'ご'}, {'ゴ'}, {'コ', '゙'}, {'こ', '゙'}, {'コ', '゙'}, {'こ', '゛'}, {'コ', '゛'}, {'コ', '゛'}}},
{[][]rune{{'げ'}, {'ゲ'}, {'ケ', '゙'}, {'け', '゙'}, {'ケ', '゙'}, {'け', '゛'}, {'ケ', '゛'}, {'ケ', '゛'}, {'ヶ', '゙'}, {'ヶ', '゛'}}},
{[][]rune{{'ぐ'}, {'グ'}, {'ク', '゙'}, {'く', '゙'}, {'ク', '゙'}, {'く', '゛'}, {'ク', '゛'}, {'ク', '゛'}}},
{[][]rune{{'ぎ'}, {'ギ'}, {'キ', '゙'}, {'き', '゙'}, {'キ', '゙'}, {'き', '゛'}, {'キ', '゛'}, {'キ', '゛'}}},
{[][]rune{{'が'}, {'ガ'}, {'カ', '゙'}, {'ヵ', '゙'}, {'カ', '゙'}, {'か', '゙'}, {'力', '゙'}, {'ヵ', '゛'}, {'カ', '゛'}, {'か', '゛'}, {'力', '゛'}, {'カ', '゛'}}},
{[][]rune{{'を'}, {'ヲ'}, {'ヲ'}}},
{[][]rune{{'わ'}, {'ワ'}, {'ワ'}, {'ヮ'}}},
{[][]rune{{'ろ'}, {'ロ'}, {'ロ'}, {'□'}, {'口'}}},
{[][]rune{{'れ'}, {'レ'}, {'レ'}}},
{[][]rune{{'る'}, {'ル'}, {'ル'}}},
{[][]rune{{'り'}, {'リ'}, {'リ'}}},
{[][]rune{{'ら'}, {'ラ'}, {'ラ'}}},
{[][]rune{{'よ'}, {'ヨ'}, {'ヨ'}, {'ョ'}, {'ょ'}, {'ョ'}}},
{[][]rune{{'ゆ'}, {'ユ'}, {'ユ'}, {'ュ'}, {'ゅ'}, {'ュ'}}},
{[][]rune{{'や'}, {'ヤ'}, {'ヤ'}, {'ャ'}, {'ゃ'}, {'ャ'}}},
{[][]rune{{'も'}, {'モ'}, {'モ'}}},
{[][]rune{{'め'}, {'メ'}, {'メ'}, {'M', 'E'}}},
{[][]rune{{'む'}, {'ム'}, {'ム'}}},
{[][]rune{{'み'}, {'ミ'}, {'ミ'}}},
{[][]rune{{'ま'}, {'マ'}, {'マ'}}},
{[][]rune{{'ほ'}, {'ホ'}, {'ホ'}}},
{[][]rune{{'へ'}, {'ヘ'}, {'ヘ'}}},
{[][]rune{{'ふ'}, {'フ'}, {'フ'}}},
{[][]rune{{'ひ'}, {'ヒ'}, {'ヒ'}}},
{[][]rune{{'は'}, {'ハ'}, {'ハ'}, {'八'}}},
{[][]rune{{'の'}, {''}, {'ノ'}}},
{[][]rune{{'ね'}, {'ネ'}, {'ネ'}}},
{[][]rune{{'ぬ'}, {'ヌ'}, {'ヌ'}}},
{[][]rune{{'に'}, {'ニ'}, {'ニ'}, {'二'}}},
{[][]rune{{'な'}, {'ナ'}, {'ナ'}}},
{[][]rune{{'と'}, {'ト'}, {'ト'}, {'┣'}, {'├'}}},
{[][]rune{{'て'}, {'テ'}, {'テ'}, {'〒'}, {'乙'}}},
{[][]rune{{'つ'}, {'ツ'}, {'ツ'}, {'っ'}, {'ッ'}, {'ッ'}}},
{[][]rune{{'ち'}, {'チ'}, {'チ'}, {'千'}}},
{[][]rune{{'た'}, {'タ'}, {'タ'}, {'夕'}}},
{[][]rune{{'そ'}, {'ソ'}, {'ソ'}}},
{[][]rune{{'せ'}, {'セ'}, {'セ'}}},
{[][]rune{{'す'}, {'ス'}, {'ス'}}},
{[][]rune{{'し'}, {'シ'}, {'シ'}}},
{[][]rune{{'さ'}, {'サ'}, {'サ'}}},
{[][]rune{{'こ'}, {'コ'}, {'コ'}}},
{[][]rune{{'け'}, {'ケ'}, {'ケ'}, {'ヶ'}}},
{[][]rune{{'く'}, {'ク'}, {'ク'}}},
{[][]rune{{'き'}, {'キ'}, {'キ'}}},
{[][]rune{{'か'}, {'カ'}, {'カ'}, {'ヵ'}, {'力'}}},
{[][]rune{{'お'}, {'オ'}, {'オ'}, {'ォ'}, {'ぉ'}, {'ォ'}}},
{[][]rune{{'え'}, {'エ'}, {'エ'}, {'ェ'}, {'ぇ'}, {'ェ'}, {'工'}}},
{[][]rune{{'う'}, {'ウ'}, {'ウ'}, {'ゥ'}, {'ぅ'}, {'ゥ'}}},
{[][]rune{{'い'}, {'イ'}, {'イ'}, {'ィ'}, {'ぃ'}, {'ィ'}}},
{[][]rune{{'あ'}, {'ア'}, {'ァ'}, {'ア'}, {'ぁ'}, {'ァ'}}},
{[][]rune{{'ー'}, {'―'}, {''}, {'-'}, {''}, {'ー'}, {'一'}}},
{[][]rune{{'9'}, {''}}},
{[][]rune{{'8'}, {''}}},
{[][]rune{{'7'}, {''}}},
{[][]rune{{'6'}, {''}}},
{[][]rune{{'5'}, {''}}},
{[][]rune{{'4'}, {''}}},
{[][]rune{{'3'}, {''}}},
{[][]rune{{'2'}, {''}}},
{[][]rune{{'1'}, {''}}},
{[][]rune{{'ぽ'}, {'ポ'}, {'ホ', '゚'}, {'ほ', '゚'}, {'ホ', '゚'}, {'ホ', '°'}, {'ほ', '°'}, {'ホ', '°'}}},
{[][]rune{{'ぺ'}, {'ペ'}, {'ヘ', '゚'}, {'へ', '゚'}, {'ヘ', '゚'}, {'ヘ', '°'}, {'へ', '°'}, {'ヘ', '°'}}},
{[][]rune{{'ぷ'}, {'プ'}, {'フ', '゚'}, {'ふ', '゚'}, {'フ', '゚'}, {'フ', '°'}, {'ふ', '°'}, {'フ', '°'}}},
{[][]rune{{'ぴ'}, {'ピ'}, {'ヒ', '゚'}, {'ひ', '゚'}, {'ヒ', '゚'}, {'ヒ', '°'}, {'ひ', '°'}, {'ヒ', '°'}}},
{[][]rune{{'ぱ'}, {'パ'}, {'ハ', '゚'}, {'は', '゚'}, {'ハ', '゚'}, {'ハ', '°'}, {'は', '°'}, {'ハ', '°'}, {'八', '゚'}, {'八', '゜'}}},
{[][]rune{{'z'}, {''}, {'Z'}, {''}, {'Ζ'}}},
{[][]rune{{'y'}, {''}, {'Y'}, {''}, {'Υ'}, {'У'}, {'у'}}},
{[][]rune{{'x'}, {''}, {'X'}, {''}, {'Χ'}, {'χ'}, {'Х'}, {'×'}, {'х'}}},
{[][]rune{{'w'}, {''}, {'W'}, {''}, {'ω'}, {'Ш'}, {'ш'}, {'щ'}}},
{[][]rune{{'v'}, {''}, {'V'}, {''}, {'ν'}, {'υ'}}},
{[][]rune{{'u'}, {''}, {'U'}, {''}, {'μ'}, {''}}},
{[][]rune{{'t'}, {''}, {'T'}, {''}, {'Τ'}, {'τ'}, {'Т'}, {'т'}}},
{[][]rune{{'s'}, {''}, {'S'}, {''}, {'∫'}, {''}, {'$'}}},
{[][]rune{{'r'}, {''}, {'R'}, {''}, {'Я'}, {'я'}}},
{[][]rune{{'q'}, {''}, {'Q'}, {''}}},
{[][]rune{{'p'}, {''}, {'P'}, {''}, {'Ρ'}, {'ρ'}, {'Р'}, {'р'}}},
{[][]rune{{'o'}, {''}, {'O'}, {''}, {'○'}, {'Ο'}, {'ο'}, {'О'}, {'о'}, {'◯'}, {''}, {'0'}, {''}}},
{[][]rune{{'n'}, {''}, {'N'}, {''}, {'Ν'}, {'η'}, {'ン'}, {'ん'}, {'ン'}}},
{[][]rune{{'m'}, {''}, {'M'}, {''}, {'Μ'}, {'М'}, {'м'}}},
{[][]rune{{'l'}, {''}, {'L'}, {''}, {'|'}}},
{[][]rune{{'k'}, {''}, {'K'}, {''}, {'Κ'}, {'κ'}, {'К'}, {'к'}}},
{[][]rune{{'j'}, {''}, {'J'}, {''}}},
{[][]rune{{'i'}, {''}, {'I'}, {''}, {'Ι'}}},
{[][]rune{{'h'}, {''}, {'H'}, {''}, {'Η'}, {'Н'}, {'н'}}},
{[][]rune{{'f'}, {''}, {'F'}, {''}}},
{[][]rune{{'g'}, {''}, {'G'}, {''}}},
{[][]rune{{'e'}, {''}, {'E'}, {''}, {'Ε'}, {'ε'}, {'Е'}, {'Ё'}, {'е'}, {'ё'}, {'∈'}}},
{[][]rune{{'d'}, {''}, {'D'}, {''}}},
{[][]rune{{'c'}, {''}, {'C'}, {'С'}, {'с'}, {''}, {'℃'}}},
{[][]rune{{'b'}, {''}, {''}, {'B'}, {'β'}, {'Β'}, {'В'}, {'в'}, {'ъ'}, {'ь'}, {'♭'}}},
{[][]rune{{'\''}, {''}}},
{[][]rune{{'a'}, {''}, {''}, {'A'}, {'α'}, {'@'}, {''}, {'а'}, {'Å'}, {'А'}, {'Α'}}},
{[][]rune{{'"'}, {'”'}}},
{[][]rune{{'%'}, {''}}},
}
for _, smcGroup := range smcData {
for _, smcPair := range smcGroup.charGroup {
smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[0]))[0])
if len(smcPair) > 1 {
smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[1]))[0])
} else {
smc.WriteUint16(0)
}
}
smc.WriteUint32(0)
}
filters.WriteUint32(uint32(len(smc.Data())))
filters.WriteBytes(smc.Data())
filters.WriteNullTerminatedBytes([]byte("nam"))
nam := byteframe.NewByteFrame()
nam.SetLE()
for _, word := range namNGWords {
parts := stringsupport.ToNGWord(word)
nam.WriteUint32(uint32(len(parts)))
for _, part := range parts {
nam.WriteUint16(part)
var i int16
j := int16(-1)
for _, smcGroup := range smcData {
if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) {
j = i
break
}
i += int16(len(smcGroup.charGroup) + 1)
}
nam.WriteInt16(j)
}
nam.WriteUint16(0)
nam.WriteInt16(-1)
}
filters.WriteUint32(uint32(len(nam.Data())))
filters.WriteBytes(nam.Data())
filters.WriteNullTerminatedBytes([]byte("msg"))
msg := byteframe.NewByteFrame()
msg.SetLE()
for _, word := range msgNGWords {
parts := stringsupport.ToNGWord(word)
msg.WriteUint32(uint32(len(parts)))
for _, part := range parts {
msg.WriteUint16(part)
var i int16
j := int16(-1)
for _, smcGroup := range smcData {
if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) {
j = i
break
}
i += int16(len(smcGroup.charGroup) + 1)
}
msg.WriteInt16(j)
}
msg.WriteUint16(0)
msg.WriteInt16(-1)
}
filters.WriteUint32(uint32(len(msg.Data())))
filters.WriteBytes(msg.Data())
bf.WriteUint16(uint16(len(filters.Data())))
bf.WriteBytes(filters.Data())
if s.client == VITA || s.client == PS3 || s.client == PS4 { if s.client == VITA || s.client == PS3 || s.client == PS4 {
var psnUser string var psnUser string
s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser) s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)