53 Commits

Author SHA1 Message Date
wish
ce364720de alpelo object system backport test commit 2025-10-05 16:19:34 +11:00
wish
c4ec2efde5 alpelo object system backport test commit 2025-10-05 16:18:22 +11:00
wish
aad3b088b9 alpelo object system backport test commit 2025-10-05 16:14:39 +11:00
wish
dd36f367a9 alpelo object system backport test commit 2025-10-05 16:10:47 +11:00
wish
8f3624d589 Update README.md 2025-05-16 18:10:13 +10:00
wish
8d1c6a79e2 S6 compatibility fix 2025-05-04 01:47:30 +10:00
wish
d83c784452 Create 26-fix-mail.sql 2025-04-10 23:35:59 +10:00
wish
ee7b099deb Create 25-fix-rasta-id.sql 2025-04-10 23:33:40 +10:00
wish
ef3212da11 Rename fix-weekly-stamps.sql to 24-fix-weekly-stamps.sql 2025-04-10 23:33:08 +10:00
wish
3d0114ce22 fix MhfAcquireCafeItem cost in G1-G5.2 2025-04-01 20:58:33 +11:00
wish
c539905c1d implement SysWaitStageBinary timeout 2025-03-16 23:04:42 +11:00
wish
69a2a7ca3b MhfReadMercenaryW changes 2025-03-16 15:51:10 +11:00
wish
7e58a26693 Merge remote-tracking branch 'origin/main' 2025-03-10 11:38:23 +11:00
wish
8c219be30f fix InfoGuild response on <G10 2025-03-10 11:38:00 +11:00
wish
f9cfb07760 Merge pull request #134 from ZeruLight/fix/packet-queue
fix/packet-queue
2025-03-09 14:41:10 +11:00
wish
f2862ea4b8 prevent concurrent map write to questCache 2025-03-08 11:41:57 +11:00
wish
3e71c308f4 minor MhfInfoGuild changes 2025-03-06 23:01:22 +11:00
wish
3c0d29ed41 fix invalidateSessions 2025-02-27 20:06:05 +11:00
wish
09f09e230d Merge pull request #133 from ZeruLight/main 2025-02-24 12:28:10 +11:00
wish
5028355cfc prevent nil pointer in MhfGetGuildManageRight 2025-02-23 21:46:00 +11:00
wish
ba04b79bd8 partially revert guildMembers query 2025-02-23 19:29:02 +11:00
wish
06c01153f6 Merge branch 'main' into fix/packet-queue 2025-02-18 03:27:01 +11:00
wish
296bc36dbc Merge pull request #131 from ZeruLight/dependabot/go_modules/golang.org/x/net-0.33.0
Bump golang.org/x/net from 0.23.0 to 0.33.0
2025-02-18 03:23:19 +11:00
wish
79b65281f9 Merge branch 'main' into fix/packet-queue 2025-02-18 03:21:11 +11:00
wish
d17d97fefc update workflow 2025-02-18 03:20:43 +11:00
dependabot[bot]
0bf39b9caf Bump golang.org/x/net from 0.23.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 16:19:53 +00:00
wish
51ae16541f update workflow 2025-02-18 03:18:54 +11:00
wish
d86f50bd29 Merge branch 'main' into fix/packet-queue 2025-02-18 03:16:41 +11:00
wish
7c61f70590 add invalidateSessions 2025-02-18 03:16:18 +11:00
wish
d1dfc3fbb1 packet queue fix proposal 2025-02-18 03:12:09 +11:00
wish
b3305d1185 Update handlers_cafe.go 2025-01-15 00:38:12 +11:00
wish
4eed6a9738 add playtime chat command 2024-11-09 18:20:49 +11:00
wish
ab7bb0d004 revert packet concatenation 2024-11-09 17:39:14 +11:00
wish
7bf2fd5b8f revert packet concatenation 2024-11-09 17:22:43 +11:00
wish
366ec833d5 Merge pull request #130 from Brentdbr/ncafe
modified NetcafeDefaults to contain the retail Netcafe point rewards.
2024-10-20 11:32:58 +11:00
Brentdbr
a4fa55f9c9 modified NetcafeDefaults to contain the retail Netcafe point rewards. 2024-10-20 02:26:47 +02:00
wish
2215afca2b Merge pull request #121 from ZeruLight/dependabot/go_modules/golang.org/x/net-0.23.0
Bump golang.org/x/net from 0.18.0 to 0.23.0
2024-10-20 11:08:30 +11:00
wish
4348aa02a8 temporarily fix rasta expiration 2024-10-09 00:51:51 +11:00
wish
8191994acb add LoopDelay config option 2024-10-08 22:26:00 +11:00
wish
436e30f83d remove duplicate updateRights call 2024-10-08 21:07:13 +11:00
wish
34e84f31df ignore empty packet buffer 2024-10-08 21:06:52 +11:00
wish
1432e8f2b8 Merge remote-tracking branch 'origin/main' 2024-10-08 20:43:20 +11:00
wish
edd357fe50 concatenate packets during send 2024-10-08 20:42:42 +11:00
stratic-dev
ae32951671 Add troubleshooting for setup on docker and add opcode dec and hex to logger 2024-10-05 22:56:53 +01:00
stratic-dev
2d48d63263 added ./bin to ignore when building dockerfile 2024-10-05 04:36:37 +01:00
wish
b20969ddc6 emulate retail semaphore logic 2024-10-03 21:56:06 +10:00
wish
8f68e10f1d Merge remote-tracking branch 'origin/main' 2024-10-01 23:02:08 +10:00
wish
2c5896814f update ico resources 2024-10-01 23:01:15 +10:00
wish
8a55c5ff89 fix inflated festa rewards 2024-09-27 01:12:16 +10:00
wish
7d760bd3b4 fix EntranceServer clan member list limits 2024-08-08 22:16:39 +10:00
wish
04008fceb8 rewrite ReadMercenaryW handler 2024-08-08 22:16:09 +10:00
wish
1ab6940b01 add extra fields to Distributions 2024-08-08 22:14:35 +10:00
dependabot[bot]
ef99cc7659 Bump golang.org/x/net from 0.18.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.18.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.18.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 12:40:55 +00:00
47 changed files with 469 additions and 1153 deletions

1
.dockerignore Normal file
View File

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

View File

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

View File

@@ -28,7 +28,7 @@ If you want to modify or compile Erupe yourself, please read on.
## Installation
1. Bring up a fresh database by using the [backup file attached with the latest release](https://github.com/ZeruLight/Erupe/releases/latest/download/SCHEMA.sql).
2. Run each script under [patch-schema](./patch-schema) as they introduce newer schema.
2. Run each script under [patch-schema](./schemas/patch-schema) as they introduce newer schema.
3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup.
4. Run `go build` or `go run .` to compile Erupe.

View File

@@ -21,9 +21,11 @@
"QuestCacheExpiry": 300,
"CommandPrefix": "!",
"AutoCreateAccount": true,
"LoopDelay": 50,
"DefaultCourses": [1, 23, 24],
"EarthDebug": false,
"EarthMonsters": [116, 107, 2, 36],
"EarthStatus": 0,
"EarthID": 0,
"EarthMonsters": [0, 0, 0, 0],
"SaveDumps": {
"Enabled": true,
"RawEnabled": false,
@@ -168,6 +170,11 @@
"Enabled": true,
"Description": "Toggle the Quest timer",
"Prefix": "timer"
}, {
"Name": "Playtime",
"Enabled": true,
"Description": "Show your playtime",
"Prefix": "playtime"
}
],
"Courses": [

View File

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

View File

@@ -64,3 +64,7 @@ if you want all the logs and you want it to be in an attached state
```bash
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/spf13/viper v1.17.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/text v0.14.0
golang.org/x/text v0.21.0
)
require (
@@ -31,8 +31,8 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
gopkg.in/ini.v1 v1.67.0 // 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-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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
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-20190226205417-e64efc72b421/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-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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
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/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=
@@ -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.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
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-20190308202827-9d24e82272b4/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.ItemID = bf.ReadUint16()
m.Quant = bf.ReadUint16()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
if _config.ErupeConfig.RealClientMode >= _config.G6 {
m.PointCost = bf.ReadUint32()
} else {
m.PointCost = uint32(bf.ReadUint16())

View File

@@ -2,7 +2,6 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -11,9 +10,9 @@ import (
// MsgMhfGetBreakSeibatuLevelReward represents the MSG_MHF_GET_BREAK_SEIBATU_LEVEL_REWARD
type MsgMhfGetBreakSeibatuLevelReward struct {
AckHandle uint32
Unk0 uint32
EarthMonster int32
AckHandle uint32
Unk0 uint32
Unk1 int32
}
// Opcode returns the ID associated with this packet type.
@@ -25,8 +24,7 @@ func (m *MsgMhfGetBreakSeibatuLevelReward) Opcode() network.PacketID {
func (m *MsgMhfGetBreakSeibatuLevelReward) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.EarthMonster = bf.ReadInt32()
fmt.Printf("MsgMhfGetBreakSeibatuLevelReward: Unk0:[%d] EarthMonster:[%d] \n\n", m.Unk0, m.EarthMonster)
m.Unk1 = bf.ReadInt32()
return nil
}

View File

@@ -2,7 +2,6 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -11,12 +10,12 @@ import (
// MsgMhfGetFixedSeibatuRankingTable represents the MSG_MHF_GET_FIXED_SEIBATU_RANKING_TABLE
type MsgMhfGetFixedSeibatuRankingTable struct {
AckHandle uint32
Unk0 uint32
Unk1 int32
EarthMonster int32
Unk3 int32
Unk4 int32
AckHandle uint32
Unk0 uint32
Unk1 int32
Unk2 int32
Unk3 int32
Unk4 int32
}
// Opcode returns the ID associated with this packet type.
@@ -29,10 +28,9 @@ func (m *MsgMhfGetFixedSeibatuRankingTable) Parse(bf *byteframe.ByteFrame, ctx *
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadInt32()
m.EarthMonster = bf.ReadInt32()
m.Unk2 = bf.ReadInt32()
m.Unk3 = bf.ReadInt32()
m.Unk4 = bf.ReadInt32()
fmt.Printf("MsgMhfGetFixedSeibatuRankingTable: Unk0:[%d] Unk1:[%d] EarthMonster:[%d] Unk3:[%d] Unk4:[%d]\n\n", m.Unk0, m.Unk1, m.EarthMonster, m.Unk3, m.Unk4)
return nil
}

View File

@@ -2,7 +2,6 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -11,11 +10,11 @@ import (
// MsgMhfGetWeeklySeibatuRankingReward represents the MSG_MHF_GET_WEEKLY_SEIBATU_RANKING_REWARD
type MsgMhfGetWeeklySeibatuRankingReward struct {
AckHandle uint32
Unk0 uint32
Operation uint32
ID uint32
EarthMonster uint32
AckHandle uint32
Unk0 uint32
Unk1 uint32
Unk2 uint32
Unk3 uint32
}
// Opcode returns the ID associated with this packet type.
@@ -27,10 +26,9 @@ func (m *MsgMhfGetWeeklySeibatuRankingReward) Opcode() network.PacketID {
func (m *MsgMhfGetWeeklySeibatuRankingReward) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.Operation = bf.ReadUint32()
m.ID = bf.ReadUint32()
m.EarthMonster = bf.ReadUint32()
fmt.Printf("MsgMhfGetWeeklySeibatuRankingReward: Unk0:[%d] Operation:[%d] ID:[%d] EarthMonster:[%d]\n\n", m.Unk0, m.Operation, m.ID, m.EarthMonster)
m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32()
m.Unk3 = bf.ReadUint32()
return nil
}

View File

@@ -12,7 +12,7 @@ import (
type MsgMhfReadBeatLevelAllRanking struct {
AckHandle uint32
Unk0 uint32
MonsterID int32
GuildID int32
Unk2 int32
}
@@ -25,7 +25,7 @@ func (m *MsgMhfReadBeatLevelAllRanking) Opcode() network.PacketID {
func (m *MsgMhfReadBeatLevelAllRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.MonsterID = bf.ReadInt32()
m.GuildID = bf.ReadInt32()
m.Unk2 = bf.ReadInt32()
return nil
}

View File

@@ -2,7 +2,6 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -11,9 +10,9 @@ import (
// MsgMhfReadLastWeekBeatRanking represents the MSG_MHF_READ_LAST_WEEK_BEAT_RANKING
type MsgMhfReadLastWeekBeatRanking struct {
AckHandle uint32
Unk0 uint32
EarthMonster int32
AckHandle uint32
Unk0 uint32
Unk1 int32
}
// Opcode returns the ID associated with this packet type.
@@ -25,9 +24,7 @@ func (m *MsgMhfReadLastWeekBeatRanking) Opcode() network.PacketID {
func (m *MsgMhfReadLastWeekBeatRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.EarthMonster = bf.ReadInt32()
fmt.Printf("MsgMhfGetFixedSeibatuRankingTable: Unk0:[%d] EarthMonster:[%d] \n\n", m.Unk0, m.EarthMonster)
m.Unk1 = bf.ReadInt32()
return nil
}

Binary file not shown.

View File

@@ -1,9 +0,0 @@
BEGIN;
INSERT INTO public.event_quests (max_players, quest_type, quest_id, mark) VALUES
(0,33,54257,0),
(0,33,54258,0),
(0,33,54277,0),
(0,33,54370,0);
END;

View File

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

View File

@@ -1,6 +0,0 @@
BEGIN;
-- Add 'earth' to the event_type ENUM type
ALTER TYPE event_type ADD VALUE 'earth';
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

@@ -1,5 +0,0 @@
BEGIN;
ALTER TABLE public.characters ADD COLUMN IF NOT EXISTS conquest_data BYTEA;
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 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

@@ -30,6 +30,18 @@ func stubEnumerateNoResults(s *Session, ackHandle uint32) {
doAckBufSucceed(s, ackHandle, enumBf.Data())
}
func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(s.server.erupeConfig.EarthID))
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(uint32(len(data)))
for i := range data {
bf.WriteBytes(data[i].Data())
}
doAckBufSucceed(s, ackHandle, bf.Data())
}
func doAckBufSucceed(s *Session, ackHandle uint32, data []byte) {
s.QueueSendMHF(&mhfpacket.MsgSysAck{
AckHandle: ackHandle,
@@ -76,7 +88,7 @@ func updateRights(s *Session) {
Rights: s.courses,
UnkSize: 0,
}
s.QueueSendMHF(update)
s.QueueSendMHFNonBlocking(update)
}
func handleMsgHead(s *Session, p mhfpacket.MHFPacket) {}
@@ -131,7 +143,6 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.token = pkt.LoginTokenString
s.Unlock()
updateRights(s)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp
@@ -172,7 +183,6 @@ func logoutPlayer(s *Session) {
delete(s.server.sessions, s.rawConn)
}
s.rawConn.Close()
delete(s.server.objectIDs, s)
s.server.Unlock()
for _, stage := range s.server.stages {
@@ -181,7 +191,7 @@ func logoutPlayer(s *Session) {
for _, sess := range s.server.sessions {
for rSlot := range stage.reservedClientSlots {
if sess.charID == rSlot && sess.stage != nil && sess.stage.id[3:5] != "Qs" {
sess.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
sess.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{})
}
}
}
@@ -1103,8 +1113,66 @@ func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfKickExportForce(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.EarthStatus)
bf.WriteInt32(s.server.erupeConfig.EarthID)
for i, m := range s.server.erupeConfig.EarthMonsters {
if _config.ErupeConfig.RealClientMode <= _config.G9 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfRegistSpabiTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetEarthValue(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthValue)
type EarthValues struct {
Value []uint32
}
var earthValues []EarthValues
switch pkt.ReqType {
case 1:
earthValues = []EarthValues{
{[]uint32{1, 312, 0, 0, 0, 0}},
{[]uint32{2, 99, 0, 0, 0, 0}},
}
case 2:
earthValues = []EarthValues{
{[]uint32{1, 5771, 0, 0, 0, 0}},
{[]uint32{2, 1847, 0, 0, 0, 0}},
}
case 3:
earthValues = []EarthValues{
{[]uint32{1001, 36, 0, 0, 0, 0}},
{[]uint32{9001, 3, 0, 0, 0, 0}},
{[]uint32{9002, 10, 300, 0, 0, 0}},
}
}
var data []*byteframe.ByteFrame
for _, i := range earthValues {
bf := byteframe.NewByteFrame()
for _, j := range i.Value {
bf.WriteUint32(j)
}
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfDebugPostValue(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetRandFromTable(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -166,7 +166,7 @@ func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
FROM characters ch
WHERE ch.id = $1
) >= 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())
} else {
for rows.Next() {

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ import (
type Distribution struct {
ID uint32 `db:"id"`
Deadline time.Time `db:"deadline"`
Rights uint32 `db:"rights"`
TimesAcceptable uint16 `db:"times_acceptable"`
TimesAccepted uint16 `db:"times_accepted"`
MinHR int16 `db:"min_hr"`
@@ -23,7 +24,7 @@ type Distribution struct {
MaxGR int16 `db:"max_gr"`
EventName string `db:"event_name"`
Description string `db:"description"`
Data []byte `db:"data"`
Selection bool `db:"selection"`
}
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
@@ -32,7 +33,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
var itemDists []Distribution
bf := byteframe.NewByteFrame()
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_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,
@@ -60,7 +61,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
for _, dist := range itemDists {
bf.WriteUint32(dist.ID)
bf.WriteUint32(uint32(dist.Deadline.Unix()))
bf.WriteUint32(0) // Unk
bf.WriteUint32(dist.Rights)
bf.WriteUint16(dist.TimesAcceptable)
bf.WriteUint16(dist.TimesAccepted)
if _config.ErupeConfig.RealClientMode >= _config.G9 {
@@ -79,7 +80,11 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(0) // Unk
}
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 {
bf.WriteUint16(0) // Unk

View File

@@ -1,173 +0,0 @@
package channelserver
import (
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"log"
"time"
)
func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(uint32(len(data)))
for i := range data {
bf.WriteBytes(data[i].Data())
}
doAckBufSucceed(s, ackHandle, bf.Data())
}
func handleMsgMhfGetEarthValue(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthValue)
type EarthValues struct {
Value []uint32
}
var earthValues []EarthValues
switch pkt.ReqType {
case 1:
earthValues = []EarthValues{
// {Block, DureSlays, Unk, Unk, Unk, Unk}
{[]uint32{1, 100, 0, 0, 0, 0}},
{[]uint32{2, 100, 0, 0, 0, 0}},
}
case 2:
earthValues = []EarthValues{
// {Block, Floors?, Unk, Unk, Unk, Unk}
{[]uint32{1, 5771, 0, 0, 0, 0}},
{[]uint32{2, 1847, 0, 0, 0, 0}},
}
case 3:
earthValues = []EarthValues{
{[]uint32{1001, 36, 0, 0, 0, 0}}, //getTouhaHistory
{[]uint32{9001, 3, 0, 0, 0, 0}}, //getKohouhinDropStopFlag // something to do with ttcSetDisableFlag?
{[]uint32{9002, 10, 300, 0, 0, 0}}, //getKohouhinForceValue
}
}
var data []*byteframe.ByteFrame
for _, i := range earthValues {
bf := byteframe.NewByteFrame()
for _, j := range i.Value {
bf.WriteUint32(j)
}
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func cleanupEarthStatus(s *Session) {
s.server.db.Exec(`DELETE FROM events WHERE event_type='earth'`)
s.server.db.Exec(`UPDATE characters SET conquest_data=NULL`)
}
func generateEarthStatusTimestamps(s *Session, start uint32, debug bool) []uint32 {
timestamps := make([]uint32, 4)
midnight := TimeMidnight()
if start == 0 || TimeAdjusted().Unix() > int64(start)+1814400 {
cleanupEarthStatus(s)
start = uint32(midnight.Add(24 * time.Hour).Unix())
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('earth', to_timestamp($1)::timestamp without time zone)", start)
}
if debug {
timestamps[0] = uint32(TimeWeekStart().Unix())
timestamps[1] = uint32(TimeWeekNext().Unix())
timestamps[2] = uint32(TimeWeekNext().Add(time.Duration(7) * time.Hour * 24).Unix())
timestamps[3] = uint32(TimeWeekNext().Add(time.Duration(14) * time.Hour * 24).Unix())
} else {
timestamps[0] = start
timestamps[1] = timestamps[0] + 604800
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 604800
}
return timestamps
}
func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthStatus)
bf := byteframe.NewByteFrame()
var earthTimestamps []uint32
var debug = s.server.erupeConfig.EarthDebug
earthId, earthStart := int32(0x01BEEFEE), uint32(0)
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='earth'")
if rows == nil {
log.Println("No rows found")
} else {
for rows.Next() {
rows.Scan(&earthId, &earthStart)
}
}
earthTimestamps = generateEarthStatusTimestamps(s, earthStart, debug)
// Conquest
if uint32(TimeAdjusted().Unix()) > earthTimestamps[0] {
bf.WriteUint32(earthTimestamps[0]) // Start
bf.WriteUint32(earthTimestamps[1]) // End
bf.WriteInt32(1) //Conquest Earth Status ID //1 and 2 UNK the difference
bf.WriteInt32(earthId) //ID
} else {
bf.WriteUint32(earthTimestamps[1]) // Start
bf.WriteUint32(earthTimestamps[2]) // End
bf.WriteInt32(2) //Conquest Earth Status ID //1 and 2 UNK the difference
bf.WriteInt32(earthId) //ID
}
for i, m := range s.server.erupeConfig.EarthMonsters {
//Changed from G9 to G8 to get conquest working in g9.1
if _config.ErupeConfig.RealClientMode <= _config.G8 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
// Pallone
if uint32(TimeAdjusted().Unix()) > earthTimestamps[1] {
bf.WriteUint32(earthTimestamps[1]) // Start
bf.WriteUint32(earthTimestamps[2]) // End
bf.WriteInt32(11) //Pallone Earth Status ID //11 is Fest //12 is Reward
bf.WriteInt32(earthId + 1) //ID
} else {
bf.WriteUint32(earthTimestamps[2]) // Start
bf.WriteUint32(earthTimestamps[3]) // End
bf.WriteInt32(12) //Pallone Earth Status ID //11 is Fest //12 is Reward
bf.WriteInt32(earthId + 1) //ID
}
for i, m := range s.server.erupeConfig.EarthMonsters {
//Changed from G9 to G8 to get conquest working in g9.1
if _config.ErupeConfig.RealClientMode <= _config.G8 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
// Tower
if uint32(TimeAdjusted().Unix()) > earthTimestamps[2] {
bf.WriteUint32(earthTimestamps[2]) // Start
bf.WriteUint32(earthTimestamps[3]) // End
bf.WriteInt32(21) //Tower Earth Status ID
bf.WriteInt32(earthId + 2) //ID
for i, m := range s.server.erupeConfig.EarthMonsters {
if _config.ErupeConfig.RealClientMode <= _config.G8 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

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

View File

@@ -971,14 +971,21 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
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 {
bf.WriteUint16(0x00)
bf.WriteUint16(0)
} else if guild.LeaderCharID == s.charID {
bf.WriteUint16(0x01)
bf.WriteUint16(1)
} else {
bf.WriteUint16(0x02)
bf.WriteUint16(2)
}
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
@@ -1098,20 +1105,25 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(applicant.CharID)
bf.WriteUint32(0)
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)
}
}
type UnkGuildInfo struct {
Unk0 uint8
type Activity struct {
Pass uint8
Unk1 uint8
Unk2 uint8
}
unkGuildInfo := []UnkGuildInfo{}
bf.WriteUint8(uint8(len(unkGuildInfo)))
for _, info := range unkGuildInfo {
bf.WriteUint8(info.Unk0)
activity := []Activity{
// 1,0,0 = ok
// 0,0,0 = ng
}
bf.WriteUint8(uint8(len(activity)))
for _, info := range activity {
bf.WriteUint8(info.Pass)
bf.WriteUint8(info.Unk1)
bf.WriteUint8(info.Unk2)
}
@@ -1159,7 +1171,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} 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)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil && s.prevGuildID != 0 {
if guild == nil || s.prevGuildID != 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
s.prevGuildID = 0
if guild == nil || err != nil {

View File

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

View File

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

View File

@@ -21,7 +21,6 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
data = make([]byte, 9)
}
doAckBufSucceed(s, pkt.AckHandle, data)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
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) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
if pkt.Op > 0 {
bf := byteframe.NewByteFrame()
var pactID uint32
var name string
var cid uint32
bf := byteframe.NewByteFrame()
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
if pactID > 0 {
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
bf.WriteUint8(1) // numLends
bf.WriteUint32(pactID)
bf.WriteUint32(cid)
bf.WriteBool(false) // ?
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
} else {
bf.WriteUint8(0)
}
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)
var pactID, cid uint32
var name string
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
if pactID > 0 {
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
bf.WriteUint8(1) // numLends
bf.WriteUint32(pactID)
bf.WriteUint32(cid)
bf.WriteBool(true) // Escort enabled
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
} else {
resp.WriteBool(true)
resp.WriteBytes(data)
bf.WriteUint8(0)
}
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) {

View File

@@ -12,7 +12,7 @@ func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
s.stage.Lock()
newObj := &Object{
id: s.NextObjectID(),
id: s.getObjectId(),
ownerCharID: s.charID,
x: pkt.X,
y: pkt.Y,

View File

@@ -85,6 +85,10 @@ func BackportQuest(data []byte) []byte {
}
}
}
if _config.ErupeConfig.RealClientMode <= _config.S6 {
binary.LittleEndian.PutUint32(data[16:20], binary.LittleEndian.Uint32(data[8:12]))
}
return data
}
@@ -238,8 +242,10 @@ func loadQuestFile(s *Session, questId int) []byte {
}
questBody.WriteBytes(newStrings.Data())
s.server.questCacheLock.Lock()
s.server.questCacheData[questId] = questBody.Data()
s.server.questCacheTime[questId] = time.Now()
s.server.questCacheLock.Unlock()
return questBody.Data()
}

View File

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

View File

@@ -2,741 +2,85 @@ package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
)
type BreakSeibatuLevelReward struct {
Item int32
Quantity int32
Level int32
Unk int32
}
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBreakSeibatuLevelReward)
var data []*byteframe.ByteFrame
var weeklySeibatuRankingRewards []BreakSeibatuLevelReward
switch pkt.EarthMonster {
case 116:
weeklySeibatuRankingRewards = []BreakSeibatuLevelReward{
{8, 3, 2, 0},
{8, 3, 2, 0},
{8, 3, 2, 0},
{8, 3, 3, 0},
{8, 3, 3, 0},
{8, 3, 3, 0},
{8, 3, 3, 0}}
case 107:
weeklySeibatuRankingRewards = []BreakSeibatuLevelReward{
{4, 3, 1, 0},
{4, 3, 2, 0},
{4, 3, 3, 0},
{4, 3, 4, 0},
{4, 3, 5, 0}}
case 2:
weeklySeibatuRankingRewards = []BreakSeibatuLevelReward{
{5, 3, 1, 0},
{5, 3, 2, 0},
{5, 3, 3, 0},
{5, 3, 4, 0},
{5, 3, 5, 0}}
case 36:
weeklySeibatuRankingRewards = []BreakSeibatuLevelReward{
{7, 3, 1, 0},
{7, 3, 2, 0},
{7, 3, 3, 0},
{7, 3, 4, 0},
{7, 3, 5, 0}}
default:
weeklySeibatuRankingRewards = []BreakSeibatuLevelReward{
{1, 3, 1, 0},
{1, 3, 2, 0},
{1, 3, 3, 0},
{1, 3, 4, 0},
{1, 3, 5, 0}}
}
for _, seibatuData := range weeklySeibatuRankingRewards {
bf := byteframe.NewByteFrame()
bf.WriteInt32(seibatuData.Item)
bf.WriteInt32(seibatuData.Quantity)
bf.WriteInt32(seibatuData.Level)
bf.WriteInt32(seibatuData.Unk)
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
bf := byteframe.NewByteFrame()
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type WeeklySeibatuRankingRewardData struct {
Index0 int32 //Place Start
Index1 int32 //Place Finish
Index2 uint32 // UNK
DistributionType int32 //Type 7201:Item 7202:N Points 7203:Guild Contribution Points
ItemID int32
Amount int32
}
type WeeklySeibatuRankingRewards struct {
Unk0 int32
ItemID int32
Amount uint32
PlaceFrom int32
PlaceTo int32
type WeeklySeibatuRankingReward struct {
Unk0 int32
Unk1 int32
Unk2 uint32
Unk3 int32
Unk4 int32
Unk5 int32
}
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward)
var data []*byteframe.ByteFrame
var weeklySeibatuRankingRewards []WeeklySeibatuRankingRewards
var weeklySeibatuRankingRewardsData []WeeklySeibatuRankingRewardData
switch pkt.Operation {
case 1:
//Conquest Data
switch pkt.ID { // Seems to align with EarthStatus 1 and 2 for Conquest
case 1:
switch pkt.EarthMonster {
case 116:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
case 107:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
case 2:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
case 36:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
}
case 2:
switch pkt.EarthMonster {
case 116:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
case 107:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
case 2:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
case 36:
weeklySeibatuRankingRewards = []WeeklySeibatuRankingRewards{
{0, 2, 3, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 6, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 15, 1, 100},
{0, 2, 25, 1, 100},
{0, 2, 2, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 4, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 9, 101, 1000},
{0, 2, 30, 101, 1000},
{0, 2, 2, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 4, 1000, 1001},
{0, 2, 6, 1000, 1001},
{0, 2, 6, 1000, 1001},
}
}
}
case 3:
//Pallone Festival Data
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
//Unk0
//Unk1
//Unk2
//Unk3,
//ROUTE, (Crashes if it doesnt exist be careful with values )
//Status 1 = Only Now ! 2= Unk 3= Disabled}
//Route 0
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 1
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 2
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 3
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 4
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 5
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 6
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 7
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 8
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 9
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
//Route 10
{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
}
// 0 = Max 7 Routes so value 6
//ZZ looks like it only works up to Route 2
case 5:
//Event Reward Data
switch pkt.ID {
//243400 = Route 0
//243401 = Route 1
//I have a sneaky suspicion that the above massive array is feeding into this somehow....
case 240031:
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
{1, 1, 1, 7201, 12068, 1}}
case 240041:
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
{0, 0, 1, 7201, 12068, 1}}
case 240042:
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
{0, 0, 2, 7201, 12068, 1}}
case 240051:
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
{0, 0, 1, 7201, 12068, 1}}
case 240052:
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
{1, 1, 1, 7201, 12068, 1},
}
case 260001:
//Tower Dure Kill Reward
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
//Can only have 10 in each dist (It disapears otherwise) Looks like up to dist 4 is implemented
//This is claimable for every Dure Kill, Make cliamable in bulk or mandatory claim per kill
//{unk,unk,dist,seiabtuType,ItemID,Value}
{0, 0, 1, 7201, 11463, 1},
{0, 0, 1, 7201, 11464, 1},
{0, 0, 1, 7201, 11163, 1},
{0, 0, 1, 7201, 11159, 5},
{0, 0, 1, 7201, 11160, 5},
{0, 0, 1, 7201, 11161, 5},
{0, 0, 2, 7201, 12506, 1},
{0, 0, 2, 7201, 10355, 1},
{0, 0, 2, 7201, 11163, 1},
{0, 0, 2, 7201, 11159, 5},
{0, 0, 2, 7201, 11160, 5},
{0, 0, 2, 7201, 11161, 5},
}
case 260003:
//Tower Floor Reward
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
//Adjust Floors done in database to make blue
//This is claimable for every Floor Climbed across dist 1 and 2
//{Floor,unk,unk,seiabtuType,ItemID,Value}
{1, 0, 0, 7201, 11158, 1},
{2, 0, 0, 7201, 11173, 1},
{3, 0, 0, 7201, 10813, 3},
{4, 0, 0, 7201, 11163, 1},
{5, 0, 0, 7201, 11164, 1},
{6, 0, 0, 7201, 11389, 3},
{6, 0, 0, 7201, 11381, 1},
{7, 0, 0, 7201, 11384, 1},
{8, 0, 0, 7201, 11159, 10},
{9, 0, 0, 7201, 11160, 10},
{10, 0, 0, 7201, 11161, 10},
{11, 0, 0, 7201, 11265, 2},
{11, 0, 0, 7201, 7279, 2},
{12, 0, 0, 7201, 11381, 1},
{13, 0, 0, 7201, 11384, 1},
{14, 0, 0, 7201, 11381, 1},
{15, 0, 0, 7201, 11384, 1},
{15, 0, 0, 7201, 11464, 1},
{16, 0, 0, 7201, 11381, 1},
{17, 0, 0, 7201, 11384, 1},
{18, 0, 0, 7201, 11381, 1},
{19, 0, 0, 7201, 11384, 1},
{20, 0, 0, 7201, 10778, 3},
{21, 0, 0, 7201, 11265, 2},
{21, 0, 0, 7201, 7279, 2},
{22, 0, 0, 7201, 11381, 1},
{23, 0, 0, 7201, 11384, 1},
{24, 0, 0, 7201, 11381, 1},
{25, 0, 0, 7201, 11389, 3},
{25, 0, 0, 7201, 11286, 4},
{26, 0, 0, 7201, 11384, 1},
{27, 0, 0, 7201, 11381, 1},
{28, 0, 0, 7201, 11384, 1},
{29, 0, 0, 7201, 11381, 1},
{30, 0, 0, 7201, 11209, 3},
{31, 0, 0, 7201, 11265, 2},
{31, 0, 0, 7201, 7279, 2},
{32, 0, 0, 7201, 11159, 10},
{33, 0, 0, 7201, 11463, 1},
{34, 0, 0, 7201, 11160, 10},
{35, 0, 0, 7201, 11286, 4},
{36, 0, 0, 7201, 11161, 10},
{38, 0, 0, 7201, 11384, 1},
{39, 0, 0, 7201, 11164, 1},
{40, 0, 0, 7201, 10813, 3},
{41, 0, 0, 7201, 11265, 2},
{41, 0, 0, 7201, 7280, 2},
{43, 0, 0, 7201, 11381, 1},
{45, 0, 0, 7201, 11286, 4},
{47, 0, 0, 7201, 11384, 1},
{48, 0, 0, 7201, 11358, 1},
{50, 0, 0, 7201, 11356, 1},
{51, 0, 0, 7201, 11265, 2},
{51, 0, 0, 7201, 7280, 2},
{53, 0, 0, 7201, 11381, 2},
{55, 0, 0, 7201, 11357, 1},
{57, 0, 0, 7201, 11384, 1},
{60, 0, 0, 7201, 11286, 4},
{61, 0, 0, 7201, 11265, 2},
{61, 0, 0, 7201, 7280, 2},
{63, 0, 0, 7201, 11381, 2},
{66, 0, 0, 7201, 11463, 1},
{67, 0, 0, 7201, 11384, 1},
{70, 0, 0, 7201, 11286, 4},
{71, 0, 0, 7201, 11265, 2},
{71, 0, 0, 7201, 7280, 2},
{73, 0, 0, 7201, 11381, 2},
{77, 0, 0, 7201, 11384, 1},
{79, 0, 0, 7201, 11164, 1},
{80, 0, 0, 7201, 11286, 6},
{81, 0, 0, 7201, 11265, 2},
{81, 0, 0, 7201, 7281, 1},
{83, 0, 0, 7201, 11381, 2},
{85, 0, 0, 7201, 11464, 1},
{87, 0, 0, 7201, 11384, 1},
{90, 0, 0, 7201, 11286, 6},
{91, 0, 0, 7201, 11265, 2},
{91, 0, 0, 7201, 7281, 1},
{93, 0, 0, 7201, 11381, 2},
{95, 0, 0, 7201, 10778, 3},
{97, 0, 0, 7201, 11384, 1},
{99, 0, 0, 7201, 11463, 1},
{100, 0, 0, 7201, 11286, 6},
{101, 0, 0, 7201, 11265, 2},
{101, 0, 0, 7201, 7281, 1},
{103, 0, 0, 7201, 11381, 2},
{107, 0, 0, 7201, 11384, 1},
{110, 0, 0, 7201, 11286, 6},
{113, 0, 0, 7201, 11381, 2},
{115, 0, 0, 7201, 11164, 1},
{117, 0, 0, 7201, 11384, 1},
{120, 0, 0, 7201, 11286, 12},
{123, 0, 0, 7201, 11381, 2},
{127, 0, 0, 7201, 11384, 1},
{130, 0, 0, 7201, 11286, 12},
{132, 0, 0, 7201, 11381, 2},
{134, 0, 0, 7201, 11384, 1},
{136, 0, 0, 7201, 11381, 2},
{138, 0, 0, 7201, 11384, 1},
{140, 0, 0, 7201, 11286, 12},
{142, 0, 0, 7201, 11382, 1},
{144, 0, 0, 7201, 11385, 1},
{145, 0, 0, 7201, 11464, 1},
{146, 0, 0, 7201, 11382, 1},
{148, 0, 0, 7201, 11385, 1},
{150, 0, 0, 7201, 11164, 1},
{155, 0, 0, 7201, 11382, 1},
{160, 0, 0, 7201, 11209, 3},
{165, 0, 0, 7201, 11385, 1},
{170, 0, 0, 7201, 11159, 10},
{175, 0, 0, 7201, 11382, 1},
{180, 0, 0, 7201, 11160, 10},
{185, 0, 0, 7201, 11385, 1},
{190, 0, 0, 7201, 11161, 10},
{195, 0, 0, 7201, 11382, 1},
{200, 0, 0, 7201, 11159, 15},
{210, 0, 0, 7201, 11160, 15},
{220, 0, 0, 7201, 11385, 1},
{235, 0, 0, 7201, 11382, 2},
{250, 0, 0, 7201, 11161, 15},
{265, 0, 0, 7201, 11159, 20},
{280, 0, 0, 7201, 11385, 1},
{300, 0, 0, 7201, 11160, 20},
{315, 0, 0, 7201, 11382, 2},
{330, 0, 0, 7201, 11385, 1},
{350, 0, 0, 7201, 11161, 20},
{365, 0, 0, 7201, 11382, 2},
{380, 0, 0, 7201, 11385, 1},
{400, 0, 0, 7201, 11159, 25},
{415, 0, 0, 7201, 11382, 2},
{430, 0, 0, 7201, 11385, 1},
{450, 0, 0, 7201, 11160, 25},
{465, 0, 0, 7201, 11382, 2},
{480, 0, 0, 7201, 11385, 1},
{500, 0, 0, 7201, 11161, 25},
{525, 0, 0, 7201, 11382, 2},
{550, 0, 0, 7201, 11385, 1},
{575, 0, 0, 7201, 11159, 25},
{600, 0, 0, 7201, 11382, 2},
{625, 0, 0, 7201, 11385, 1},
{650, 0, 0, 7201, 11160, 25},
{675, 0, 0, 7201, 11382, 2},
{700, 0, 0, 7201, 11385, 1},
{725, 0, 0, 7201, 11161, 25},
{750, 0, 0, 7201, 11382, 2},
{775, 0, 0, 7201, 11385, 1},
{800, 0, 0, 7201, 11159, 25},
{825, 0, 0, 7201, 11382, 2},
{850, 0, 0, 7201, 11385, 1},
{875, 0, 0, 7201, 11160, 25},
{900, 0, 0, 7201, 11382, 2},
{925, 0, 0, 7201, 11385, 1},
{950, 0, 0, 7201, 11161, 25},
{975, 0, 0, 7201, 11382, 2},
{1000, 0, 0, 7201, 11385, 1},
{1025, 0, 0, 7201, 11159, 25},
{1050, 0, 0, 7201, 11382, 2},
{1075, 0, 0, 7201, 11385, 1},
{1100, 0, 0, 7201, 11160, 25},
{1125, 0, 0, 7201, 11382, 2},
{1150, 0, 0, 7201, 11385, 1},
{1200, 0, 0, 7201, 11161, 25},
{1235, 0, 0, 7201, 11382, 2},
{1270, 0, 0, 7201, 11385, 1},
{1305, 0, 0, 7201, 11159, 25},
{1340, 0, 0, 7201, 11382, 2},
{1375, 0, 0, 7201, 11385, 1},
{1410, 0, 0, 7201, 11160, 25},
{1445, 0, 0, 7201, 11382, 2},
{1480, 0, 0, 7201, 11385, 1},
{1500, 0, 0, 7201, 11161, 25},
}
default:
//Covers all Pallone Requests... for now
weeklySeibatuRankingRewardsData = []WeeklySeibatuRankingRewardData{
//1st
{1, 0, 0, 7202, 10, 10000},
{1, 1, 0, 7201, 10, 30},
{1, 1, 0, 7201, 10, 18},
{1, 1, 0, 7201, 10, 18},
//2nd - 3rd
{2, 3, 0, 7202, 10, 6000},
{2, 3, 0, 7201, 10, 15},
{2, 3, 0, 7201, 10, 9},
{2, 3, 0, 7201, 10, 9},
//4th -10th
{4, 10, 0, 7202, 10, 5500},
{4, 10, 0, 7201, 10, 12},
{4, 10, 0, 7201, 10, 9},
}
}
weeklySeibatuRankingRewards := []WeeklySeibatuRankingReward{
{0, 0, 0, 0, 0, 0},
}
if pkt.Operation == 1 {
for _, seibatuData := range weeklySeibatuRankingRewards {
bf := byteframe.NewByteFrame()
bf.WriteInt32(seibatuData.Unk0)
bf.WriteInt32(seibatuData.ItemID)
bf.WriteUint32(seibatuData.Amount)
bf.WriteInt32(seibatuData.PlaceFrom)
bf.WriteInt32(seibatuData.PlaceTo)
data = append(data, bf)
}
} else {
for _, seibatuData := range weeklySeibatuRankingRewardsData {
bf := byteframe.NewByteFrame()
bf.WriteInt32(seibatuData.Index0)
bf.WriteInt32(seibatuData.Index1)
bf.WriteUint32(seibatuData.Index2)
bf.WriteInt32(seibatuData.DistributionType)
bf.WriteInt32(seibatuData.ItemID)
bf.WriteInt32(seibatuData.Amount)
data = append(data, bf)
}
for _, reward := range weeklySeibatuRankingRewards {
bf := byteframe.NewByteFrame()
bf.WriteInt32(reward.Unk0)
bf.WriteInt32(reward.Unk1)
bf.WriteUint32(reward.Unk2)
bf.WriteInt32(reward.Unk3)
bf.WriteInt32(reward.Unk4)
bf.WriteInt32(reward.Unk5)
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
type FixedSeibatuRankingTable struct {
Rank int32
Level int32
Name string
}
func handleMsgMhfGetFixedSeibatuRankingTable(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetFixedSeibatuRankingTable)
var fixedSeibatuRankingTable []FixedSeibatuRankingTable
//Interestingly doesn't trigger the pkt on EarthStatus 1 But menu option is there is this Seibatu instead?
switch pkt.EarthMonster {
case 116:
fixedSeibatuRankingTable = []FixedSeibatuRankingTable{
{1, 1, "Hunter 1"},
{2, 1, "Hunter 2"},
{3, 1, "Hunter 3"},
{4, 1, "Hunter 4"},
{5, 1, "Hunter 5"},
{6, 1, "Hunter 6"},
{7, 1, "Hunter 7"},
{8, 1, "Hunter 8"},
{9, 1, "Hunter 9"},
}
case 107:
fixedSeibatuRankingTable = []FixedSeibatuRankingTable{
{1, 2, "Hunter 1"},
{2, 2, "Hunter 2"},
{3, 2, "Hunter 3"},
{4, 2, "Hunter 4"},
{5, 2, "Hunter 5"},
{6, 2, "Hunter 6"},
{7, 2, "Hunter 7"},
{8, 2, "Hunter 8"},
{9, 2, "Hunter 9"},
}
case 2:
fixedSeibatuRankingTable = []FixedSeibatuRankingTable{
{1, 3, "Hunter 1"},
{2, 3, "Hunter 2"},
{3, 3, "Hunter 3"},
{4, 3, "Hunter 4"},
{5, 3, "Hunter 5"},
{6, 3, "Hunter 6"},
{7, 3, "Hunter 7"},
{8, 3, "Hunter 8"},
{9, 3, "Hunter 9"},
}
case 36:
fixedSeibatuRankingTable = []FixedSeibatuRankingTable{
{1, 4, "Hunter 1"},
{2, 4, "Hunter 2"},
{3, 4, "Hunter 3"},
{4, 4, "Hunter 4"},
{5, 4, "Hunter 5"},
{6, 4, "Hunter 6"},
{7, 4, "Hunter 7"},
{8, 4, "Hunter 8"},
{9, 4, "Hunter 9"},
}
default:
fixedSeibatuRankingTable = []FixedSeibatuRankingTable{
{1, 1, "Hunter 1"},
{2, 1, "Hunter 2"},
{3, 1, "Hunter 3"},
{4, 1, "Hunter 4"},
{5, 1, "Hunter 5"},
{6, 1, "Hunter 6"},
{7, 1, "Hunter 7"},
{8, 1, "Hunter 8"},
{9, 1, "Hunter 9"},
}
}
bf := byteframe.NewByteFrame()
for _, seibatuData := range fixedSeibatuRankingTable {
bf.WriteInt32(seibatuData.Rank)
bf.WriteInt32(seibatuData.Level)
bf.WriteBytes(stringsupport.PaddedString(seibatuData.Name, 32, true))
}
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteBytes(make([]byte, 32))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfReadBeatLevel(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadBeatLevel)
var data []byte
err := s.server.db.QueryRow(`SELECT conquest_data FROM characters WHERE id = $1`, s.charID).Scan(&data)
if err != nil || len(data) == 0 {
data = []byte{0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1}
}
bf := byteframe.NewByteFrameFromBytes(data)
// This response is fixed and will never change on JP,
// but I've left it dynamic for possible other client differences.
resp := byteframe.NewByteFrame()
for i := 0; i < int(pkt.ValidIDCount); i++ {
resp.WriteUint32(pkt.IDs[i])
resp.WriteUint32(bf.ReadUint32())
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(1)
resp.WriteUint32(1)
resp.WriteUint32(1)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfReadLastWeekBeatRanking(s *Session, p mhfpacket.MHFPacket) {
//Controls the monster headings for the other menus
pkt := p.(*mhfpacket.MsgMhfReadLastWeekBeatRanking)
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(pkt.EarthMonster))
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
bf := byteframe.NewByteFrame()
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateBeatLevel(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateBeatLevel)
bf := byteframe.NewByteFrame()
for i := 0; i < 4; i++ {
bf.WriteInt32(pkt.Data2[i])
}
s.server.db.Exec(`UPDATE characters SET conquest_data = $1 WHERE id = $2`, bf.Data(), s.charID)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

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

View File

@@ -8,6 +8,7 @@ import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
@@ -59,33 +60,29 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.Unlock()
// Tell the client to cleanup its current stage objects.
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCleanupObject{})
// Confirm the stage entry.
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
var temp mhfpacket.MHFPacket
newNotif := byteframe.NewByteFrame()
// Cast existing user data to new user
if !s.userEnteredStage {
s.userEnteredStage = true
if !s.loaded {
s.loaded = true
for _, session := range s.server.sessions {
if s == session {
if s == session || !session.loaded {
continue
}
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
newNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(newNotif, s.clientContext)
for i := 0; i < 3; i++ {
temp = &mhfpacket.MsgSysNotifyUserBinary{
CharID: session.charID,
BinaryType: uint8(i + 1),
}
newNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(newNotif, s.clientContext)
}
session.QueueSendMHF(&mhfpacket.MsgSysInsertUser{CharID: s.charID})
session.QueueSendMHF(&mhfpacket.MsgSysNotifyUserBinary{CharID: s.charID, BinaryType: 1})
session.QueueSendMHF(&mhfpacket.MsgSysNotifyUserBinary{CharID: s.charID, BinaryType: 2})
session.QueueSendMHF(&mhfpacket.MsgSysNotifyUserBinary{CharID: s.charID, BinaryType: 3})
s.QueueSendMHF(&mhfpacket.MsgSysInsertUser{CharID: session.charID})
s.QueueSendMHF(&mhfpacket.MsgSysNotifyUserBinary{CharID: session.charID, BinaryType: 1})
s.QueueSendMHF(&mhfpacket.MsgSysNotifyUserBinary{CharID: session.charID, BinaryType: 2})
s.QueueSendMHF(&mhfpacket.MsgSysNotifyUserBinary{CharID: session.charID, BinaryType: 3})
}
}
@@ -112,9 +109,8 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.stage.RUnlock()
}
newNotif.WriteUint16(0x0010) // End it.
if len(newNotif.Data()) > 2 {
s.QueueSend(newNotif.Data())
s.QueueSendNonBlocking(newNotif.Data())
}
}
@@ -238,7 +234,7 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
for charID := range s.reservationStage.reservedClientSlots {
session := s.server.FindSessionByCharID(charID)
if session != nil {
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
session.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{})
}
}
@@ -362,7 +358,7 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
for {
for i := 0; i < 10; i++ {
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
stage.Lock()
stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]
@@ -370,13 +366,15 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
if gotBinary {
doAckBufSucceed(s, pkt.AckHandle, stageBinary)
break
return
} else {
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
time.Sleep(1 * time.Second)
continue
}
}
s.logger.Warn("MsgSysWaitStageBinary stage binary timeout")
doAckBufSucceed(s, pkt.AckHandle, []byte{})
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}

View File

@@ -49,7 +49,6 @@ type Server struct {
acceptConns chan net.Conn
deleteConns chan net.Conn
sessions map[net.Conn]*Session
objectIDs map[*Session]uint16
listener net.Listener // Listener that is created when Server.Start is called.
isShuttingDown bool
@@ -75,6 +74,7 @@ type Server struct {
raviente *Raviente
questCacheLock sync.RWMutex
questCacheData map[int][]byte
questCacheTime map[int]time.Time
}
@@ -154,7 +154,6 @@ func NewServer(config *Config) *Server {
acceptConns: make(chan net.Conn),
deleteConns: make(chan net.Conn),
sessions: make(map[net.Conn]*Session),
objectIDs: make(map[*Session]uint16),
stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
@@ -207,6 +206,7 @@ func (s *Server) Start() error {
go s.acceptClients()
go s.manageSessions()
go s.invalidateSessions()
// Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
@@ -278,6 +278,35 @@ func (s *Server) manageSessions() {
}
}
func (s *Server) getObjectId() uint16 {
ids := make(map[uint16]struct{})
for _, sess := range s.sessions {
ids[sess.objectID] = struct{}{}
}
for i := uint16(1); i < 100; i++ {
if _, ok := ids[i]; !ok {
return i
}
}
s.logger.Warn("object ids overflowed", zap.Int("sessions", len(s.sessions)))
return 0
}
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.
func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
@@ -424,24 +453,13 @@ func (s *Server) FindObjectByChar(charID uint32) *Object {
return nil
}
func (s *Server) NextSemaphoreID() uint32 {
for {
exists := false
s.semaphoreIndex = s.semaphoreIndex + 1
if s.semaphoreIndex > 0xFFFF {
s.semaphoreIndex = 1
}
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true
break
}
}
if !exists {
break
func (s *Server) HasSemaphore(ses *Session) bool {
for _, semaphore := range s.semaphore {
if semaphore.host == ses {
return true
}
}
return s.semaphoreIndex
return false
}
func (s *Server) Season() uint8 {

View File

@@ -10,6 +10,7 @@ type i18n struct {
noOp string
disabled string
reload string
playtime string
kqf struct {
get string
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.disabled = "%s command is disabled"
i.commands.reload = "Reloading players..."
i.commands.playtime = "Playtime: %d hours %d minutes %d seconds"
i.commands.kqf.get = "KQF: %x"
i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
i.commands.kqf.set.success = "KQF set, please switch Land/World"

View File

@@ -22,15 +22,18 @@ type Semaphore struct {
// Max Players for Semaphore
maxPlayers uint16
host *Session
}
// 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{
name: ID,
id: s.NextSemaphoreID(),
id: s.GetSemaphoreID(),
clients: make(map[*Session]uint32),
maxPlayers: MaxPlayers,
host: s,
}
return sema
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/hex"
"erupe-ce/common/mhfcourse"
_config "erupe-ce/config"
"fmt"
"io"
"net"
@@ -15,6 +16,7 @@ import (
"erupe-ce/network"
"erupe-ce/network/clientctx"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
@@ -34,8 +36,10 @@ type Session struct {
clientContext *clientctx.ClientContext
lastPacket time.Time
objectIndex uint16
userEnteredStage bool // If the user has entered a stage before
objectID uint16
objectIndex uint16
loaded bool
stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage
@@ -48,7 +52,12 @@ type Session struct {
kqf []byte
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)
stageMoveStack *stringstack.StringStack
@@ -76,11 +85,12 @@ func NewSession(server *Server, conn net.Conn) *Session {
sendPackets: make(chan packet, 20),
clientContext: &clientctx.ClientContext{}, // Unused
lastPacket: time.Now(),
objectID: server.getObjectId(),
sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(),
ackStart: make(map[uint32]time.Time),
semaphoreID: make([]uint16, 2),
}
s.SetObjectID()
return s
}
@@ -96,22 +106,9 @@ func (s *Session) Start() {
// QueueSend queues a packet (raw []byte) to be sent.
func (s *Session) QueueSend(data []byte) {
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
select {
case s.sendPackets <- packet{data, false}:
// Enqueued data
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}
err := s.cryptConn.SendPacket(append(data, []byte{0x00, 0x10}...))
if err != nil {
s.logger.Warn("Failed to send packet")
}
}
@@ -138,6 +135,19 @@ func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) {
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.
func (s *Session) QueueAck(ackHandle uint32, data []byte) {
bf := byteframe.NewByteFrame()
@@ -150,26 +160,26 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) {
func (s *Session) sendLoop() {
var pkt packet
for {
var buf []byte
if s.closed {
return
}
for len(s.sendPackets) > 0 {
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 {
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() {
for {
if time.Now().Add(-30 * time.Second).After(s.lastPacket) {
logoutPlayer(s)
return
}
if s.closed {
logoutPlayer(s)
return
@@ -185,7 +195,7 @@ func (s *Session) recvLoop() {
return
}
s.handlePacketGroup(pkt)
time.Sleep(5 * time.Millisecond)
time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond)
}
}
@@ -272,7 +282,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
} else {
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 len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
@@ -284,30 +294,17 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
}
}
func (s *Session) SetObjectID() {
for i := uint16(1); i < 127; i++ {
exists := false
for _, j := range s.server.objectIDs {
if i == j {
exists = true
break
}
}
if !exists {
s.server.objectIDs[s] = i
return
}
}
s.server.objectIDs[s] = 0
func (s *Session) getObjectId() uint32 {
s.objectIndex++
return uint32(s.objectID)<<16 | uint32(s.objectIndex)
}
func (s *Session) NextObjectID() uint32 {
bf := byteframe.NewByteFrame()
bf.WriteUint16(s.server.objectIDs[s])
s.objectIndex++
bf.WriteUint16(s.objectIndex)
bf.Seek(0, 0)
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 {

View File

@@ -86,7 +86,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
}
}
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()
}