mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-02-04 09:15:08 +01:00
Merge branch 'main' into feature/warehouse-v2
This commit is contained in:
48
.github/workflows/docker.yml
vendored
Normal file
48
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Create and publish a Docker image
|
||||
|
||||
# Configures this workflow to run every time a tag is created.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
#
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ savedata/*/
|
||||
*.exe
|
||||
*.lnk
|
||||
*.bat
|
||||
/docker/db-data
|
||||
29
AUTHORS.md
Normal file
29
AUTHORS.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# List of AUTHORS who contributed over time to the Erupe project
|
||||
|
||||
## Point of current development
|
||||
The project is currently developed under https://github.com/ZeruLight/Erupe
|
||||
|
||||
## History of development
|
||||
Development of this project dates back to 2019, and was developed under various umbrellas over time:
|
||||
* Cappuccino (Fist/Ando/Ellie42) (The Erupe Developers), 2019-2020 (https://github.com/Ellie42/Erupe / https://github.com/ricochhet/Erupe-Legacy) (Still active closed source)
|
||||
* Einherjar Team, ????-2022 Feb (There is no git history for this period, this team's work was taken and used as a foundation for future repositories)
|
||||
* Community Edition, 2022 (https://github.com/xl3lackout/Erupe)
|
||||
* Zerulight, 2022-2023 (https://github.com/ZeruLight/Erupe)
|
||||
|
||||
## Authorship of the code
|
||||
Authorship is assigned for each commit within the git history, which is stored in these git repos:
|
||||
* https://github.com/ZeruLight/Erupe
|
||||
* https://github.com/Ellie42/Erupe
|
||||
* https://github.com/ricochhet/Erupe-Legacy
|
||||
* https://github.com/xl3lackout/Erupe
|
||||
|
||||
Note there is a divergence between Ellie42s branch and xl3lackout where history has been lost.
|
||||
|
||||
Unfortunately, we have no detailed information on the history of the Erupe pre-2022
|
||||
if somebody can provide information, please contact us, so that we can make this history available.
|
||||
|
||||
## Exceptions with third-party libraries
|
||||
The third-party libraries have their own way of addressing authorship and the authorship of commits importing/updating
|
||||
a third-party library reflects who did the importing instead of who wrote the code within the commit.
|
||||
|
||||
The Authors of third-party libraries are not explicitly mentioned, and usually is possible to obtain from the files belonging to the third-party libraries.
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM golang:1.21-alpine3.19
|
||||
|
||||
ENV GO111MODULE=on
|
||||
|
||||
WORKDIR /app/erupe
|
||||
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD [ "go", "run", "." ]
|
||||
14
README.md
14
README.md
@@ -32,6 +32,20 @@ If you want to modify or compile Erupe yourself, please read on.
|
||||
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.
|
||||
|
||||
## Docker
|
||||
|
||||
Please see the readme in [docker/README.md](./docker/README.md). At the moment this is only really good for quick installs and checking out development not for production.
|
||||
|
||||
## Schemas
|
||||
|
||||
We source control the following schemas:
|
||||
- Initialisation Schemas: These initialise the application database to a clean install from a specific version.
|
||||
- Update Schemas: These are update files they should be ran in order of version to get to the latest schema.
|
||||
- Patch Schemas: These are for development and should be ran from the lastest available update schema or initial schema. These eventually get condensed into `Update Schemas` and then deleted when updated to a new version.
|
||||
- Bundled Schemas: These are demo reference files to allow servers to be able to roll their own shops, distributions gachas and scenarios set ups.
|
||||
|
||||
Note: Patch schemas are subject to change! You should only be using them if you are following along with development.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
54
common/mhfcid/mhfcid.go
Normal file
54
common/mhfcid/mhfcid.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package mhfcid
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// ConvertCID converts a MHF Character ID String to integer
|
||||
//
|
||||
// Banned characters: 0, I, O, S
|
||||
func ConvertCID(ID string) (r uint32) {
|
||||
if len(ID) != 6 {
|
||||
return
|
||||
}
|
||||
|
||||
m := map[rune]uint32{
|
||||
'1': 0,
|
||||
'2': 1,
|
||||
'3': 2,
|
||||
'4': 3,
|
||||
'5': 4,
|
||||
'6': 5,
|
||||
'7': 6,
|
||||
'8': 7,
|
||||
'9': 8,
|
||||
'A': 9,
|
||||
'B': 10,
|
||||
'C': 11,
|
||||
'D': 12,
|
||||
'E': 13,
|
||||
'F': 14,
|
||||
'G': 15,
|
||||
'H': 16,
|
||||
'J': 17,
|
||||
'K': 18,
|
||||
'L': 19,
|
||||
'M': 20,
|
||||
'N': 21,
|
||||
'P': 22,
|
||||
'Q': 23,
|
||||
'R': 24,
|
||||
'T': 25,
|
||||
'U': 26,
|
||||
'V': 27,
|
||||
'W': 28,
|
||||
'X': 29,
|
||||
'Y': 30,
|
||||
'Z': 31,
|
||||
}
|
||||
|
||||
for i, c := range ID {
|
||||
r += m[c] * uint32(math.Pow(32, float64(i)))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package mhfcourse
|
||||
|
||||
import (
|
||||
_config "erupe-ce/config"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
@@ -66,12 +67,15 @@ func CourseExists(ID uint16, c []Course) bool {
|
||||
|
||||
// GetCourseStruct returns a slice of Course(s) from a rights integer
|
||||
func GetCourseStruct(rights uint32) ([]Course, uint32) {
|
||||
resp := []Course{{ID: 1}, {ID: 23}, {ID: 24}}
|
||||
var resp []Course
|
||||
for _, c := range _config.ErupeConfig.DefaultCourses {
|
||||
resp = append(resp, Course{ID: c})
|
||||
}
|
||||
s := Courses()
|
||||
sort.Slice(s, func(i, j int) bool {
|
||||
return s[i].ID > s[j].ID
|
||||
})
|
||||
var normalCafeCourseSet, netcafeCourseSet bool
|
||||
var normalCafeCourseSet, netcafeCourseSet, hidenCourseSet bool
|
||||
for _, course := range s {
|
||||
if rights-course.Value() < 0x80000000 {
|
||||
switch course.ID {
|
||||
@@ -88,6 +92,12 @@ func GetCourseStruct(rights uint32) ([]Course, uint32) {
|
||||
}
|
||||
netcafeCourseSet = true
|
||||
resp = append(resp, Course{ID: 30})
|
||||
case 10:
|
||||
if hidenCourseSet {
|
||||
break
|
||||
}
|
||||
hidenCourseSet = true
|
||||
resp = append(resp, Course{ID: 31})
|
||||
}
|
||||
course.Expiry = time.Date(2030, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC+9", 9*60*60))
|
||||
resp = append(resp, course)
|
||||
|
||||
@@ -6,8 +6,7 @@ import (
|
||||
|
||||
// StringStack is a basic LIFO "stack" for storing strings.
|
||||
type StringStack struct {
|
||||
Locked bool
|
||||
stack []string
|
||||
stack []string
|
||||
}
|
||||
|
||||
// New creates a new instance of StringStack
|
||||
@@ -20,20 +19,6 @@ func (s *StringStack) Set(v string) {
|
||||
s.stack = []string{v}
|
||||
}
|
||||
|
||||
// Lock freezes the StringStack
|
||||
func (s *StringStack) Lock() {
|
||||
if !s.Locked {
|
||||
s.Locked = true
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock unfreezes the StringStack
|
||||
func (s *StringStack) Unlock() {
|
||||
if s.Locked {
|
||||
s.Locked = false
|
||||
}
|
||||
}
|
||||
|
||||
// Push pushes a string onto the stack.
|
||||
func (s *StringStack) Push(v string) {
|
||||
s.stack = append(s.stack, v)
|
||||
|
||||
77
config.json
77
config.json
@@ -13,29 +13,36 @@
|
||||
"DeleteOnSaveCorruption": false,
|
||||
"ClientMode": "ZZ",
|
||||
"QuestCacheExpiry": 300,
|
||||
"ProxyPort": 0,
|
||||
"CommandPrefix": "!",
|
||||
"DevMode": true,
|
||||
"DevModeOptions": {
|
||||
"AutoCreateAccount": true,
|
||||
"AutoCreateAccount": true,
|
||||
"DefaultCourses": [1, 23, 24],
|
||||
"EarthStatus": 0,
|
||||
"EarthID": 0,
|
||||
"EarthMonsters": [0, 0, 0, 0],
|
||||
"SaveDumps": {
|
||||
"Enabled": true,
|
||||
"RawEnabled": false,
|
||||
"OutputDir": "save-backups"
|
||||
},
|
||||
"DebugOptions": {
|
||||
"CleanDB": false,
|
||||
"MaxLauncherHR": false,
|
||||
"LogInboundMessages": false,
|
||||
"LogOutboundMessages": false,
|
||||
"LogMessageData": false,
|
||||
"MaxHexdumpLength": 256,
|
||||
"DivaEvent": 0,
|
||||
"FestaEvent": -1,
|
||||
"TournamentEvent": 0,
|
||||
"DivaOverride": 0,
|
||||
"FestaOverride": -1,
|
||||
"TournamentOverride": 0,
|
||||
"DisableTokenCheck": false,
|
||||
"QuestDebugTools": false,
|
||||
"EarthStatusOverride": 0,
|
||||
"EarthIDOverride": 0,
|
||||
"EarthMonsterOverride": [0, 0, 0, 0],
|
||||
"SaveDumps": {
|
||||
"Enabled": true,
|
||||
"RawEnabled": false,
|
||||
"OutputDir": "save-backups"
|
||||
"QuestTools": false,
|
||||
"AutoQuestBackport": true,
|
||||
"ProxyPort": 0,
|
||||
"CapLink": {
|
||||
"Values": [51728, 20000, 51729, 1, 20000],
|
||||
"Key": "",
|
||||
"Host": "",
|
||||
"Port": 80
|
||||
}
|
||||
},
|
||||
"GameplayOptions": {
|
||||
@@ -51,8 +58,6 @@
|
||||
"ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]],
|
||||
"BonusQuestAllowance": 3,
|
||||
"DailyQuestAllowance": 1,
|
||||
"MezfesSoloTickets": 10,
|
||||
"MezfesGroupTickets": 4,
|
||||
"LowLatencyRaviente": false,
|
||||
"RegularRavienteMaxPlayers": 8,
|
||||
"ViolentRavienteMaxPlayers": 8,
|
||||
@@ -61,12 +66,29 @@
|
||||
"SmallBerserkRavienteMaxPlayers": 8,
|
||||
"GUrgentRate": 0.10,
|
||||
"GCPMultiplier": 1.00,
|
||||
"HRPMultiplier": 1.00,
|
||||
"HRPMultiplierNC": 1.00,
|
||||
"SRPMultiplier": 1.00,
|
||||
"SRPMultiplierNC": 1.00,
|
||||
"GRPMultiplier": 1.00,
|
||||
"GRPMultiplierNC": 1.00,
|
||||
"GSRPMultiplier": 1.00,
|
||||
"GSRPMultiplierNC": 1.00,
|
||||
"ZennyMultiplier": 1.00,
|
||||
"ZennyMultiplierNC": 1.00,
|
||||
"GZennyMultiplier": 1.00,
|
||||
"GZennyMultiplierNC": 1.00,
|
||||
"MaterialMultiplier": 1.00,
|
||||
"MaterialMultiplierNC": 1.00,
|
||||
"GMaterialMultiplier": 1.00,
|
||||
"GMaterialMultiplierNC": 1.00,
|
||||
"ExtraCarves": 0,
|
||||
"ExtraCarvesNC": 0,
|
||||
"GExtraCarves": 0,
|
||||
"GExtraCarvesNC": 0,
|
||||
"DisableHunterNavi": false,
|
||||
"MezFesSoloTickets": 5,
|
||||
"MezFesGroupTickets": 1,
|
||||
"MezFesDuration": 172800,
|
||||
"MezFesSwitchMinigame": false,
|
||||
"EnableKaijiEvent": false,
|
||||
@@ -78,7 +100,11 @@
|
||||
"Discord": {
|
||||
"Enabled": false,
|
||||
"BotToken": "",
|
||||
"RealtimeChannelID": ""
|
||||
"RelayChannel": {
|
||||
"Enabled": false,
|
||||
"MaxMessageLength": 183,
|
||||
"RelayChannelID": ""
|
||||
}
|
||||
},
|
||||
"Commands": [
|
||||
{
|
||||
@@ -121,6 +147,21 @@
|
||||
"Enabled": true,
|
||||
"Description": "Link a PlayStation Network ID to your account",
|
||||
"Prefix": "psn"
|
||||
}, {
|
||||
"Name": "Discord",
|
||||
"Enabled": true,
|
||||
"Description": "Generate a token to link your Discord account",
|
||||
"Prefix": "discord"
|
||||
}, {
|
||||
"Name": "Ban",
|
||||
"Enabled": false,
|
||||
"Description": "Ban/Temp Ban a user",
|
||||
"Prefix": "ban"
|
||||
}, {
|
||||
"Name": "Timer",
|
||||
"Enabled": true,
|
||||
"Description": "Toggle the Quest timer",
|
||||
"Prefix": "timer"
|
||||
}
|
||||
],
|
||||
"Courses": [
|
||||
|
||||
105
config/config.go
105
config/config.go
@@ -80,40 +80,23 @@ type Config struct {
|
||||
ClientMode string
|
||||
RealClientMode Mode
|
||||
QuestCacheExpiry int // Number of seconds to keep quest data cached
|
||||
ProxyPort uint16 // Forces the game to connect to a channel server proxy
|
||||
CommandPrefix string // The prefix for commands
|
||||
DevMode bool
|
||||
|
||||
DevModeOptions DevModeOptions
|
||||
GameplayOptions GameplayOptions
|
||||
Discord Discord
|
||||
Commands []Command
|
||||
Courses []Course
|
||||
Database Database
|
||||
Sign Sign
|
||||
SignV2 SignV2
|
||||
Channel Channel
|
||||
Entrance Entrance
|
||||
}
|
||||
|
||||
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
|
||||
type DevModeOptions struct {
|
||||
AutoCreateAccount bool // Automatically create accounts if they don't exist
|
||||
CleanDB bool // Automatically wipes the DB on server reset.
|
||||
MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
|
||||
LogInboundMessages bool // Log all messages sent to the server
|
||||
LogOutboundMessages bool // Log all messages sent to the clients
|
||||
LogMessageData bool // Log all bytes transferred as a hexdump
|
||||
MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
|
||||
DivaEvent int // Diva Defense event status
|
||||
FestaEvent int // Hunter's Festa event status
|
||||
TournamentEvent int // VS Tournament event status
|
||||
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
|
||||
QuestDebugTools bool // Enable various quest debug logs
|
||||
EarthStatusOverride int32
|
||||
EarthIDOverride int32
|
||||
EarthMonsterOverride []int32
|
||||
SaveDumps SaveDumpOptions
|
||||
AutoCreateAccount bool // Automatically create accounts if they don't exist
|
||||
DefaultCourses []uint16
|
||||
EarthStatus int32
|
||||
EarthID int32
|
||||
EarthMonsters []int32
|
||||
SaveDumps SaveDumpOptions
|
||||
DebugOptions DebugOptions
|
||||
GameplayOptions GameplayOptions
|
||||
Discord Discord
|
||||
Commands []Command
|
||||
Courses []Course
|
||||
Database Database
|
||||
Sign Sign
|
||||
SignV2 SignV2
|
||||
Channel Channel
|
||||
Entrance Entrance
|
||||
}
|
||||
|
||||
type SaveDumpOptions struct {
|
||||
@@ -122,6 +105,31 @@ type SaveDumpOptions struct {
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
// DebugOptions holds various debug/temporary options for use while developing Erupe.
|
||||
type DebugOptions struct {
|
||||
CleanDB bool // Automatically wipes the DB on server reset.
|
||||
MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
|
||||
LogInboundMessages bool // Log all messages sent to the server
|
||||
LogOutboundMessages bool // Log all messages sent to the clients
|
||||
LogMessageData bool // Log all bytes transferred as a hexdump
|
||||
MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
|
||||
DivaOverride int // Diva Defense event status
|
||||
FestaOverride int // Hunter's Festa event status
|
||||
TournamentOverride int // VS Tournament event status
|
||||
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
|
||||
QuestTools bool // Enable various quest debug logs
|
||||
AutoQuestBackport bool // Automatically backport quest files
|
||||
ProxyPort uint16 // Forces the game to connect to a channel server proxy
|
||||
CapLink CapLinkOptions
|
||||
}
|
||||
|
||||
type CapLinkOptions struct {
|
||||
Values []uint16
|
||||
Key string
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
|
||||
// GameplayOptions has various gameplay modifiers
|
||||
type GameplayOptions struct {
|
||||
FeaturedWeapons int // Number of Active Feature weapons to generate daily
|
||||
@@ -137,8 +145,6 @@ type GameplayOptions struct {
|
||||
ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members]
|
||||
BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily
|
||||
DailyQuestAllowance uint32 // Number of Daily Quests to allow daily
|
||||
MezfesSoloTickets uint32 // Number of solo tickets given weekly
|
||||
MezfesGroupTickets uint32 // Number of group tickets given weekly
|
||||
LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive
|
||||
RegularRavienteMaxPlayers uint8
|
||||
ViolentRavienteMaxPlayers uint8
|
||||
@@ -147,12 +153,29 @@ type GameplayOptions struct {
|
||||
SmallBerserkRavienteMaxPlayers uint8
|
||||
GUrgentRate float32 // Adjusts the rate of G Urgent quests spawning
|
||||
GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion
|
||||
HRPMultiplier float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion
|
||||
HRPMultiplierNC float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion in a NetCafe
|
||||
SRPMultiplier float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion
|
||||
SRPMultiplierNC float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion in a NetCafe
|
||||
GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion
|
||||
GRPMultiplierNC float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion in a NetCafe
|
||||
GSRPMultiplier float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion
|
||||
GSRPMultiplierNC float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion in a NetCafe
|
||||
ZennyMultiplier float32 // Adjusts the multiplier of Zenny rewarded for quest completion
|
||||
ZennyMultiplierNC float32 // Adjusts the multiplier of Zenny rewarded for quest completion in a NetCafe
|
||||
GZennyMultiplier float32 // Adjusts the multiplier of G Zenny rewarded for quest completion
|
||||
GZennyMultiplierNC float32 // Adjusts the multiplier of G Zenny rewarded for quest completion in a NetCafe
|
||||
MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion
|
||||
MaterialMultiplierNC float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion in a NetCafe
|
||||
GMaterialMultiplier float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion
|
||||
GMaterialMultiplierNC float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion in a NetCafe
|
||||
ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses
|
||||
ExtraCarvesNC uint16 // Grant n extra chances to carve ALL carcasses in a NetCafe
|
||||
GExtraCarves uint16 // Grant n extra chances to carve ALL G Rank carcasses
|
||||
GExtraCarvesNC uint16 // Grant n extra chances to carve ALL G Rank carcasses in a NetCafe
|
||||
DisableHunterNavi bool // Disables the Hunter Navi
|
||||
MezFesSoloTickets uint32 // Number of solo tickets given weekly
|
||||
MezFesGroupTickets uint32 // Number of group tickets given weekly
|
||||
MezFesDuration int // Seconds that MezFes will last for weekly (from 12AM Mon backwards)
|
||||
MezFesSwitchMinigame bool // Swaps out Volpakkun Together for Tokotoko Partnya
|
||||
EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar
|
||||
@@ -164,9 +187,15 @@ type GameplayOptions struct {
|
||||
|
||||
// Discord holds the discord integration config.
|
||||
type Discord struct {
|
||||
Enabled bool
|
||||
BotToken string
|
||||
RealtimeChannelID string
|
||||
Enabled bool
|
||||
BotToken string
|
||||
RelayChannel DiscordRelay
|
||||
}
|
||||
|
||||
type DiscordRelay struct {
|
||||
Enabled bool
|
||||
MaxMessageLength int
|
||||
RelayChannelID string
|
||||
}
|
||||
|
||||
// Command is a channelserver chat command
|
||||
|
||||
66
docker/README.md
Normal file
66
docker/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Docker for erupe
|
||||
|
||||
## Building the container
|
||||
Run the following from the route of the soruce folder. In this example we give it the tag of dev to seperate it from any other container verions.
|
||||
```bash
|
||||
docker build . -t erupe:dev
|
||||
```
|
||||
## Running the container in isolation
|
||||
This is just running the container. You can do volume mounts into the container for the `config.json` to tell it to communicate to a database. You will need to do this also for other folders such as `bin` and `savedata`
|
||||
```bash
|
||||
docker run erupe:dev
|
||||
```
|
||||
|
||||
## Docker compose
|
||||
Docker compose allows you to run multiple containers at once. The docker compose in this folder has 3 things set up.
|
||||
- postgres
|
||||
- pg admin (Admin interface to make db changes)
|
||||
- erupe
|
||||
|
||||
We automatically populate the database to the latest version on start. If you you are updating you will need to apply the new schemas manually.
|
||||
|
||||
Before we get started you should make sure the database info matches whats in the docker compose file for the environment variables `POSTGRES_PASSWORD`,`POSTGRES_USER` and `POSTGRES_DB`. You can set the host to be the service name `db`.
|
||||
|
||||
Here is a example of what you would put in the config.json if you was to leave the defaults. It is strongly recommended to change the password.
|
||||
```txt
|
||||
"Database": {
|
||||
"Host": "db",
|
||||
"Port": 5432,
|
||||
"User": "postgres",
|
||||
"Password": "password",
|
||||
"Database": "erupe"
|
||||
},
|
||||
```
|
||||
|
||||
Place this file within ./docker/config.json
|
||||
|
||||
You will need to do the same for your bins place these in ./docker/bin
|
||||
|
||||
# Setting up the web hosted materials
|
||||
Clone the Severs repo into ./docker/Severs
|
||||
|
||||
Make sure your hosts are pointing to where this is hosted
|
||||
|
||||
|
||||
|
||||
## Turning off the server safely
|
||||
```bash
|
||||
docker-compose stop
|
||||
```
|
||||
|
||||
## Turning off the server destructive
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
Make sure if you want to delete your data you delete the folders that persisted
|
||||
- ./docker/savedata
|
||||
- ./docker/db-data
|
||||
## Turning on the server again
|
||||
This boots the db pgadmin and the server in a detached state
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
if you want all the logs and you want it to be in an attached state
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
71
docker/docker-compose.yml
Normal file
71
docker/docker-compose.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
version: "3.9"
|
||||
# 1. docker-compose up db pgadmin
|
||||
# 2. Use pgadmin to restore db and also apply patch-schema
|
||||
# 3. Configure the config.json example. in docker you can point to the service name for the database i.e db
|
||||
# 4. In seperate terminal docker-compose up server
|
||||
# 5. If all went well happy hunting!
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
environment:
|
||||
# (Make sure these match config.json)
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
- POSTGRES_DB=erupe
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./db-data/:/var/lib/postgresql/data/
|
||||
- ../schemas/:/schemas/
|
||||
- ./init/setup.sh:/docker-entrypoint-initdb.d/setup.sh
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4
|
||||
restart: always
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: user@pgadmin.com
|
||||
PGADMIN_DEFAULT_PASSWORD: password
|
||||
ports:
|
||||
- "5050:80"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
server:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
# If using prebuilt container change paths and config
|
||||
build:
|
||||
context: ../
|
||||
volumes:
|
||||
- ./config.json:/app/erupe/config.json
|
||||
- ./bin:/app/erupe/bin
|
||||
- ./savedata:/app/erupe/savedata
|
||||
ports:
|
||||
# (Make sure these match config.json)
|
||||
- "53312:53312" #Sign V1
|
||||
- "8080:8080" #Sign V2
|
||||
- "53310:53310" #Entrance
|
||||
# Channels
|
||||
- "54001:54001"
|
||||
- "54002:54002"
|
||||
- "54003:54003"
|
||||
- "54004:54004"
|
||||
- "54005:54005"
|
||||
- "54006:54006"
|
||||
- "54007:54007"
|
||||
- "54008:54008"
|
||||
web:
|
||||
image: httpd:latest
|
||||
container_name: my-apache-app
|
||||
ports:
|
||||
- '80:80'
|
||||
volumes:
|
||||
- ./Servers:/usr/local/apache2/htdocs
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
22
docker/init/setup.sh
Normal file
22
docker/init/setup.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "INIT!"
|
||||
pg_restore --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --verbose /schemas/initialisation-schema/9.1-init.sql
|
||||
|
||||
|
||||
|
||||
echo "Updating!"
|
||||
|
||||
for file in /schemas/update-schema/*
|
||||
do
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -1 -f $file
|
||||
done
|
||||
|
||||
|
||||
|
||||
echo "Patching!"
|
||||
|
||||
for file in /schemas/patch-schema/*
|
||||
do
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -1 -f $file
|
||||
done
|
||||
4
go.mod
4
go.mod
@@ -10,7 +10,7 @@ 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.15.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
|
||||
golang.org/x/text v0.14.0
|
||||
)
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
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.14.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@@ -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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
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/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=
|
||||
@@ -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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/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=
|
||||
|
||||
23
main.go
23
main.go
@@ -22,13 +22,10 @@ import (
|
||||
)
|
||||
|
||||
// Temporary DB auto clean on startup for quick development & testing.
|
||||
func cleanDB(db *sqlx.DB, config *_config.Config) {
|
||||
func cleanDB(db *sqlx.DB) {
|
||||
_ = db.MustExec("DELETE FROM guild_characters")
|
||||
_ = db.MustExec("DELETE FROM guilds")
|
||||
_ = db.MustExec("DELETE FROM characters")
|
||||
if config.ProxyPort == 0 {
|
||||
_ = db.MustExec("DELETE FROM sign_sessions")
|
||||
}
|
||||
_ = db.MustExec("DELETE FROM users")
|
||||
}
|
||||
|
||||
@@ -48,11 +45,7 @@ func main() {
|
||||
|
||||
var zapLogger *zap.Logger
|
||||
config := _config.ErupeConfig
|
||||
if config.DevMode {
|
||||
zapLogger, _ = zap.NewDevelopment()
|
||||
} else {
|
||||
zapLogger, _ = zap.NewProduction()
|
||||
}
|
||||
zapLogger, _ = zap.NewDevelopment()
|
||||
|
||||
defer zapLogger.Sync()
|
||||
logger := zapLogger.Named("main")
|
||||
@@ -98,6 +91,12 @@ func main() {
|
||||
}
|
||||
|
||||
discordBot = bot
|
||||
|
||||
_, err = discordBot.Session.ApplicationCommandBulkOverwrite(discordBot.Session.State.User.ID, "", discordbot.Commands)
|
||||
if err != nil {
|
||||
preventClose(fmt.Sprintf("Discord: Failed to start, %s", err.Error()))
|
||||
}
|
||||
|
||||
logger.Info("Discord: Started successfully")
|
||||
} else {
|
||||
logger.Info("Discord: Disabled")
|
||||
@@ -126,16 +125,16 @@ func main() {
|
||||
logger.Info("Database: Started successfully")
|
||||
|
||||
// Clear stale data
|
||||
if config.ProxyPort == 0 {
|
||||
if config.DebugOptions.ProxyPort == 0 {
|
||||
_ = db.MustExec("DELETE FROM sign_sessions")
|
||||
}
|
||||
_ = db.MustExec("DELETE FROM servers")
|
||||
_ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`)
|
||||
|
||||
// Clean the DB if the option is on.
|
||||
if config.DevMode && config.DevModeOptions.CleanDB {
|
||||
if config.DebugOptions.CleanDB {
|
||||
logger.Info("Database: Started clearing...")
|
||||
cleanDB(db, config)
|
||||
cleanDB(db)
|
||||
logger.Info("Database: Finished clearing")
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ type ChatType uint8
|
||||
|
||||
// Chat types
|
||||
const (
|
||||
ChatTypeLocal ChatType = 1
|
||||
ChatTypeWorld ChatType = 0
|
||||
ChatTypeStage = 1
|
||||
ChatTypeGuild = 2
|
||||
ChatTypeAlliance = 3
|
||||
ChatTypeParty = 4
|
||||
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/crypto"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"erupe-ce/network/crypto"
|
||||
)
|
||||
|
||||
// CryptConn represents a MHF encrypted two-way connection,
|
||||
@@ -67,7 +66,7 @@ func (cc *CryptConn) ReadPacket() ([]byte, error) {
|
||||
cc.readKeyRot = uint32(cph.KeyRotDelta) * (cc.readKeyRot + 1)
|
||||
}
|
||||
|
||||
out, combinedCheck, check0, check1, check2 := crypto.Decrypt(encryptedPacketBody, cc.readKeyRot, nil)
|
||||
out, combinedCheck, check0, check1, check2 := crypto.Crypto(encryptedPacketBody, cc.readKeyRot, false, nil)
|
||||
if cph.Check0 != check0 || cph.Check1 != check1 || cph.Check2 != check2 {
|
||||
fmt.Printf("got c0 %X, c1 %X, c2 %X\n", check0, check1, check2)
|
||||
fmt.Printf("want c0 %X, c1 %X, c2 %X\n", cph.Check0, cph.Check1, cph.Check2)
|
||||
@@ -77,7 +76,7 @@ func (cc *CryptConn) ReadPacket() ([]byte, error) {
|
||||
// Attempt to bruteforce it.
|
||||
fmt.Println("Crypto out of sync? Attempting bruteforce")
|
||||
for key := byte(0); key < 255; key++ {
|
||||
out, combinedCheck, check0, check1, check2 = crypto.Decrypt(encryptedPacketBody, 0, &key)
|
||||
out, combinedCheck, check0, check1, check2 = crypto.Crypto(encryptedPacketBody, 0, false, &key)
|
||||
//fmt.Printf("Key: 0x%X\n%s\n", key, hex.Dump(out))
|
||||
if cph.Check0 == check0 && cph.Check1 == check1 && cph.Check2 == check2 {
|
||||
fmt.Printf("Bruceforce successful, override key: 0x%X\n", key)
|
||||
@@ -106,7 +105,7 @@ func (cc *CryptConn) SendPacket(data []byte) error {
|
||||
}
|
||||
|
||||
// Encrypt the data
|
||||
encData, combinedCheck, check0, check1, check2 := crypto.Encrypt(data, cc.sendKeyRot, nil)
|
||||
encData, combinedCheck, check0, check1, check2 := crypto.Crypto(data, cc.sendKeyRot, true, nil)
|
||||
|
||||
header := &CryptPacketHeader{}
|
||||
header.Pf0 = byte(((uint(len(encData)) >> 12) & 0xF3) | 3)
|
||||
@@ -123,9 +122,7 @@ func (cc *CryptConn) SendPacket(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cc.conn.Write(headerBytes)
|
||||
cc.conn.Write(encData)
|
||||
|
||||
cc.conn.Write(append(headerBytes, encData...))
|
||||
cc.sentPackets++
|
||||
cc.prevSendPacketCombinedCheck = combinedCheck
|
||||
|
||||
|
||||
@@ -6,46 +6,30 @@ var (
|
||||
_sharedCryptKey = []byte{0xDD, 0xA8, 0x5F, 0x1E, 0x57, 0xAF, 0xC0, 0xCC, 0x43, 0x35, 0x8F, 0xBB, 0x6F, 0xE6, 0xA1, 0xD6, 0x60, 0xB9, 0x1A, 0xAE, 0x20, 0x49, 0x24, 0x81, 0x21, 0xFE, 0x86, 0x2B, 0x98, 0xB7, 0xB3, 0xD2, 0x91, 0x01, 0x3A, 0x4C, 0x65, 0x92, 0x1C, 0xF4, 0xBE, 0xDD, 0xD9, 0x08, 0xE6, 0x81, 0x98, 0x1B, 0x8D, 0x60, 0xF3, 0x6F, 0xA1, 0x47, 0x24, 0xF1, 0x53, 0x45, 0xC8, 0x7B, 0x88, 0x80, 0x4E, 0x36, 0xC3, 0x0D, 0xC9, 0xD6, 0x8B, 0x08, 0x19, 0x0B, 0xA5, 0xC1, 0x11, 0x4C, 0x60, 0xF8, 0x5D, 0xFC, 0x15, 0x68, 0x7E, 0x32, 0xC0, 0x50, 0xAB, 0x64, 0x1F, 0x8A, 0xD4, 0x08, 0x39, 0x7F, 0xC2, 0xFB, 0xBA, 0x6C, 0xF0, 0xE6, 0xB0, 0x31, 0x10, 0xC1, 0xBF, 0x75, 0x43, 0xBB, 0x18, 0x04, 0x0D, 0xD1, 0x97, 0xF7, 0x23, 0x21, 0x83, 0x8B, 0xCA, 0x25, 0x2B, 0xA3, 0x03, 0x13, 0xEA, 0xAE, 0xFE, 0xF0, 0xEB, 0xFD, 0x85, 0x57, 0x53, 0x65, 0x41, 0x2A, 0x40, 0x99, 0xC0, 0x94, 0x65, 0x7E, 0x7C, 0x93, 0x82, 0xB0, 0xB3, 0xE5, 0xC0, 0x21, 0x09, 0x84, 0xD5, 0xEF, 0x9F, 0xD1, 0x7E, 0xDC, 0x4D, 0xF5, 0x7E, 0xCD, 0x45, 0x3C, 0x7F, 0xF5, 0x59, 0x98, 0xC6, 0x55, 0xFC, 0x9F, 0xA3, 0xB7, 0x74, 0xEE, 0x31, 0x98, 0xE6, 0xB7, 0xBE, 0x26, 0xF4, 0x3C, 0x76, 0xF1, 0x23, 0x7E, 0x02, 0x4E, 0x3C, 0xD1, 0xC7, 0x28, 0x23, 0x73, 0xC4, 0xD9, 0x5E, 0x0D, 0xA1, 0x80, 0xA5, 0xAA, 0x26, 0x0A, 0xA3, 0x44, 0x82, 0x74, 0xE6, 0x3C, 0x44, 0x27, 0x51, 0x0D, 0x5F, 0xC7, 0x9C, 0xD6, 0x63, 0x67, 0xA5, 0x27, 0x97, 0x38, 0xFB, 0x2D, 0xD3, 0xD6, 0x60, 0x25, 0x83, 0x4D, 0x37, 0x5B, 0x40, 0x59, 0x11, 0x77, 0x51, 0x11, 0x14, 0x18, 0x07, 0x63, 0xB1, 0x34, 0x3D, 0xB8, 0x60, 0x13, 0xC2, 0xE8, 0x13, 0x82}
|
||||
)
|
||||
|
||||
// Encrypt encrypts the given data using MHF's custom encryption+checksum method.
|
||||
// if a overrideByteKey value is supplied (!= nil), it will be used to override the derived/truncated key byte.
|
||||
func Encrypt(data []byte, key uint32, overrideByteKey *byte) (outputData []byte, combinedCheck uint16, check0 uint16, check1 uint16, check2 uint16) {
|
||||
return _generalCrypt(data, key, 0, overrideByteKey)
|
||||
}
|
||||
|
||||
// Decrypt decrypts the given data using MHF's custom decryption+checksum method.
|
||||
// if a overrideByteKey value is supplied (!= nil), it will be used to override the derived/truncated key byte.
|
||||
func Decrypt(data []byte, key uint32, overrideByteKey *byte) (outputData []byte, combinedCheck uint16, check0 uint16, check1 uint16, check2 uint16) {
|
||||
return _generalCrypt(data, key, 1, overrideByteKey)
|
||||
}
|
||||
|
||||
// _generalCrypt is a generalized MHF crypto function that can perform both encryption and decryption,
|
||||
// Crypto is a generalized MHF crypto function that can perform both encryption and decryption,
|
||||
// these two crypto operations are combined into a single function because they shared most of their logic.
|
||||
// encrypt: cryptType==0
|
||||
// decrypt: cryptType==1
|
||||
func _generalCrypt(data []byte, rotKey uint32, cryptType int, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) {
|
||||
func Crypto(data []byte, rotKey uint32, encrypt bool, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) {
|
||||
cryptKeyTruncByte := byte(((rotKey >> 1) % 999983) & 0xFF)
|
||||
if overrideByteKey != nil {
|
||||
cryptKeyTruncByte = *overrideByteKey
|
||||
}
|
||||
|
||||
derivedCryptKey := int32((uint32(len(data)) * (uint32(cryptKeyTruncByte) + 1)) & 0xFFFFFFFF)
|
||||
derivedCryptKey := (uint32(len(data)) * (uint32(cryptKeyTruncByte) + 1)) & 0xFFFFFFFF
|
||||
sharedBufIdx := byte(1)
|
||||
accumulator0 := uint32(0)
|
||||
accumulator1 := uint32(0)
|
||||
accumulator2 := uint32(0)
|
||||
var accumulator0, accumulator1, accumulator2 uint32
|
||||
|
||||
var outputData []byte
|
||||
if cryptType == 0 {
|
||||
if encrypt {
|
||||
for i := 0; i < len(data); i++ {
|
||||
// Do the encryption for this iteration
|
||||
encKeyIdx := int32(((uint32(derivedCryptKey) >> 10) ^ uint32(data[i])) & 0xFF)
|
||||
derivedCryptKey = (0x4FD * (derivedCryptKey + 1))
|
||||
encKeyIdx := ((derivedCryptKey >> 10) ^ uint32(data[i])) & 0xFF
|
||||
derivedCryptKey = 1277*derivedCryptKey + 1277
|
||||
encKeyByte := _encryptKey[encKeyIdx]
|
||||
|
||||
// Update the checksum accumulators.
|
||||
accumulator2 = uint32((accumulator2 + (uint32(sharedBufIdx) * uint32(data[i]))) & 0xFFFFFFFF)
|
||||
accumulator1 = uint32((accumulator1 + uint32(encKeyIdx)) & 0xFFFFFFFF)
|
||||
accumulator0 = uint32((accumulator0 + (uint32(encKeyByte)<<(i&7))&0xFFFFFFFF) & 0xFFFFFFFF)
|
||||
accumulator2 = accumulator2 + (uint32(sharedBufIdx) * uint32(data[i]))
|
||||
accumulator1 = accumulator1 + encKeyIdx
|
||||
accumulator0 = accumulator0 + uint32(encKeyByte)<<(i&7)
|
||||
|
||||
// Append the output.
|
||||
outputData = append(outputData, _sharedCryptKey[sharedBufIdx]^encKeyByte)
|
||||
@@ -53,32 +37,32 @@ func _generalCrypt(data []byte, rotKey uint32, cryptType int, overrideByteKey *b
|
||||
// Update the sharedBufIdx for the next iteration.
|
||||
sharedBufIdx = data[i]
|
||||
}
|
||||
|
||||
} else if cryptType == 1 {
|
||||
} else {
|
||||
for i := 0; i < len(data); i++ {
|
||||
// Do the decryption for this iteration
|
||||
oldSharedBufIdx := sharedBufIdx
|
||||
tIdx := data[i] ^ _sharedCryptKey[sharedBufIdx]
|
||||
decKeyByte := _decryptKey[tIdx]
|
||||
sharedBufIdx = byte(((uint32(derivedCryptKey) >> 10) ^ uint32(decKeyByte)) & 0xFF)
|
||||
sharedBufIdx = byte((derivedCryptKey >> 10) ^ uint32(decKeyByte))
|
||||
|
||||
// Update the checksum accumulators.
|
||||
accumulator0 = (accumulator0 + ((uint32(tIdx) << (i & 7)) & 0xFFFFFFFF))
|
||||
accumulator1 = (accumulator1 + uint32(decKeyByte)) & 0xFFFFFFFF
|
||||
accumulator2 = (accumulator2 + ((uint32(oldSharedBufIdx) * uint32(sharedBufIdx)) & 0xFFFFFFFF)) & 0xFFFFFFFF
|
||||
accumulator0 = accumulator0 + uint32(tIdx)<<(i&7)
|
||||
accumulator1 = accumulator1 + uint32(decKeyByte)
|
||||
accumulator2 = accumulator2 + uint32(oldSharedBufIdx)*uint32(sharedBufIdx)
|
||||
|
||||
// Append the output.
|
||||
outputData = append(outputData, sharedBufIdx)
|
||||
|
||||
// Update the key pos for next iteration.
|
||||
derivedCryptKey = (0x4FD * (derivedCryptKey + 1))
|
||||
derivedCryptKey = 1277*derivedCryptKey + 1277
|
||||
}
|
||||
}
|
||||
|
||||
combinedCheck := uint16((accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2)) & 0xFFFF)
|
||||
check0 := uint16((accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16)) & 0xFFFF)
|
||||
check1 := uint16((accumulator1 ^ ((accumulator1 & 0xFFFF0000) >> 16)) & 0xFFFF)
|
||||
check2 := uint16((accumulator2 ^ ((accumulator2 & 0xFFFF0000) >> 16)) & 0xFFFF)
|
||||
var check [4]uint16
|
||||
check[0] = uint16(accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2))
|
||||
check[1] = uint16(accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16))
|
||||
check[2] = uint16(accumulator1 ^ ((accumulator1 & 0xFFFF0000) >> 16))
|
||||
check[3] = uint16(accumulator2 ^ ((accumulator2 & 0xFFFF0000) >> 16))
|
||||
|
||||
return outputData, combinedCheck, check0, check1, check2
|
||||
return outputData, check[0], check[1], check[2], check[3]
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestEncrypt(t *testing.T) {
|
||||
for k, tt := range tests {
|
||||
testname := fmt.Sprintf("encrypt_test_%d", k)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
out, cc, c0, c1, c2 := Encrypt(tt.decryptedData, tt.key, nil)
|
||||
out, cc, c0, c1, c2 := Crypto(tt.decryptedData, tt.key, true, nil)
|
||||
if cc != tt.ecc {
|
||||
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
|
||||
} else if c0 != tt.ec0 {
|
||||
@@ -86,7 +86,7 @@ func TestDecrypt(t *testing.T) {
|
||||
for k, tt := range tests {
|
||||
testname := fmt.Sprintf("decrypt_test_%d", k)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
out, cc, c0, c1, c2 := Decrypt(tt.encryptedData, tt.key, nil)
|
||||
out, cc, c0, c1, c2 := Crypto(tt.decryptedData, tt.key, false, nil)
|
||||
if cc != tt.ecc {
|
||||
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
|
||||
} else if c0 != tt.ec0 {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package mhfpacket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"errors"
|
||||
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
)
|
||||
|
||||
// MsgMhfChargeFesta represents the MSG_MHF_CHARGE_FESTA
|
||||
type MsgMhfChargeFesta struct {
|
||||
AckHandle uint32
|
||||
FestaID uint32
|
||||
GuildID uint32
|
||||
Souls int
|
||||
AckHandle uint32
|
||||
FestaID uint32
|
||||
GuildID uint32
|
||||
Souls []uint16
|
||||
Auto bool
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
@@ -23,15 +24,14 @@ func (m *MsgMhfChargeFesta) Opcode() network.PacketID {
|
||||
|
||||
// Parse parses the packet from binary
|
||||
func (m *MsgMhfChargeFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.FestaID = bf.ReadUint32()
|
||||
m.GuildID = bf.ReadUint32()
|
||||
m.Souls = 0
|
||||
for i := bf.ReadUint16(); i > 0; i-- {
|
||||
m.Souls += int(bf.ReadUint16())
|
||||
}
|
||||
_ = bf.ReadUint8() // Unk
|
||||
return nil
|
||||
m.AckHandle = bf.ReadUint32()
|
||||
m.FestaID = bf.ReadUint32()
|
||||
m.GuildID = bf.ReadUint32()
|
||||
for i := bf.ReadUint16(); i > 0; i-- {
|
||||
m.Souls = append(m.Souls, bf.ReadUint16())
|
||||
}
|
||||
m.Auto = bf.ReadBool()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build builds a binary packet from the current data.
|
||||
|
||||
BIN
rsrc_windows_amd64.syso
Normal file
BIN
rsrc_windows_amd64.syso
Normal file
Binary file not shown.
BIN
schemas/initialisation-schema/9.1-init.sql
Normal file
BIN
schemas/initialisation-schema/9.1-init.sql
Normal file
Binary file not shown.
@@ -7,8 +7,8 @@ CREATE TABLE IF NOT EXISTS tower (
|
||||
tsp INT,
|
||||
block1 INT,
|
||||
block2 INT,
|
||||
skills TEXT DEFAULT '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0',
|
||||
gems TEXT DEFAULT '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
|
||||
skills TEXT,
|
||||
gems TEXT
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS guild_characters
|
||||
6
schemas/patch-schema/16-discord-password-resets.sql
Normal file
6
schemas/patch-schema/16-discord-password-resets.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ADD COLUMN discord_token text;
|
||||
ALTER TABLE IF EXISTS public.users ADD COLUMN discord_id text;
|
||||
|
||||
END;
|
||||
12
schemas/patch-schema/17-op-accounts.sql
Normal file
12
schemas/patch-schema/17-op-accounts.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ADD COLUMN op boolean;
|
||||
|
||||
CREATE TABLE public.bans
|
||||
(
|
||||
user_id integer NOT NULL,
|
||||
expires timestamp with time zone,
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
|
||||
END;
|
||||
5
schemas/patch-schema/18-timer-toggle.sql
Normal file
5
schemas/patch-schema/18-timer-toggle.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS timer bool;
|
||||
|
||||
END;
|
||||
15
schemas/patch-schema/19-festa-submissions.sql
Normal file
15
schemas/patch-schema/19-festa-submissions.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE festa_submissions (
|
||||
character_id int NOT NULL,
|
||||
guild_id int NOT NULL,
|
||||
trial_type int NOT NULL,
|
||||
souls int NOT NULL,
|
||||
timestamp timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE guild_characters DROP COLUMN souls;
|
||||
|
||||
ALTER TYPE festival_colour RENAME TO festival_color;
|
||||
|
||||
END;
|
||||
241
schemas/update-schema/9.2-update.sql
Normal file
241
schemas/update-schema/9.2-update.sql
Normal file
@@ -0,0 +1,241 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS public.fpoint_items;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.fpoint_items (
|
||||
id serial PRIMARY KEY,
|
||||
item_type integer,
|
||||
item_id integer,
|
||||
quantity integer,
|
||||
fpoints integer,
|
||||
trade_type integer
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD bonus_quests INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD daily_quests INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD promo_points INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_characters ADD rp_today INT DEFAULT 0;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_characters ADD rp_yesterday INT DEFAULT 0;
|
||||
|
||||
UPDATE public.characters SET savemercenary = NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD rasta_id INT;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD pact_id INT;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD stampcard INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters DROP COLUMN IF EXISTS gacha_prem;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters DROP COLUMN IF EXISTS gacha_trial;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters DROP COLUMN IF EXISTS frontier_points;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ADD IF NOT EXISTS gacha_premium INT;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ADD IF NOT EXISTS gacha_trial INT;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ADD IF NOT EXISTS frontier_points INT;
|
||||
|
||||
DROP TABLE IF EXISTS public.gacha_shop;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.gacha_shop (
|
||||
id SERIAL PRIMARY KEY,
|
||||
min_gr INTEGER,
|
||||
min_hr INTEGER,
|
||||
name TEXT,
|
||||
url_banner TEXT,
|
||||
url_feature TEXT,
|
||||
url_thumbnail TEXT,
|
||||
wide BOOLEAN,
|
||||
recommended BOOLEAN,
|
||||
gacha_type INTEGER,
|
||||
hidden BOOLEAN
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS public.gacha_shop_items;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.gacha_entries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
gacha_id INTEGER,
|
||||
entry_type INTEGER,
|
||||
item_type INTEGER,
|
||||
item_number INTEGER,
|
||||
item_quantity INTEGER,
|
||||
weight INTEGER,
|
||||
rarity INTEGER,
|
||||
rolls INTEGER,
|
||||
frontier_points INTEGER,
|
||||
daily_limit INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.gacha_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
entry_id INTEGER,
|
||||
item_type INTEGER,
|
||||
item_id INTEGER,
|
||||
quantity INTEGER
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS public.stepup_state;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.gacha_stepup (
|
||||
gacha_id INTEGER,
|
||||
step INTEGER,
|
||||
character_id INTEGER
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS public.lucky_box_state;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.gacha_box (
|
||||
gacha_id INTEGER,
|
||||
entry_id INTEGER,
|
||||
character_id INTEGER
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS public.login_boost_state;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.login_boost (
|
||||
char_id INTEGER,
|
||||
week_req INTEGER,
|
||||
expiration TIMESTAMP WITH TIME ZONE,
|
||||
reset TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD COLUMN mezfes BYTEA;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ALTER COLUMN daily_time TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ALTER COLUMN guild_post_checked TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ALTER COLUMN boost_time TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ADD COLUMN IF NOT EXISTS cafe_reset TIMESTAMP WITHOUT TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ALTER COLUMN cafe_reset TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.distribution ALTER COLUMN deadline TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.events ALTER COLUMN start_time TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.feature_weapon ALTER COLUMN start_time TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.feature_weapon
|
||||
(
|
||||
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
featured INTEGER NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_alliances ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_applications ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_characters ALTER COLUMN joined_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_posts ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.characters ALTER COLUMN daily_time TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guilds ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.mail ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.stamps ALTER COLUMN hl_next TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.stamps ALTER COLUMN ex_next TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.titles ALTER COLUMN unlocked_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.titles ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ALTER COLUMN last_login TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ALTER COLUMN return_expires TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_meals DROP COLUMN IF EXISTS expires;
|
||||
|
||||
ALTER TABLE IF EXISTS public.guild_meals ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
DROP TABLE IF EXISTS public.account_ban;
|
||||
|
||||
DROP TABLE IF EXISTS public.account_history;
|
||||
|
||||
DROP TABLE IF EXISTS public.account_moderation;
|
||||
|
||||
DROP TABLE IF EXISTS public.account_sub;
|
||||
|
||||
DROP TABLE IF EXISTS public.history;
|
||||
|
||||
DROP TABLE IF EXISTS public.questlists;
|
||||
|
||||
DROP TABLE IF EXISTS public.schema_migrations;
|
||||
|
||||
DROP TABLE IF EXISTS public.user_binaries;
|
||||
|
||||
DROP PROCEDURE IF EXISTS raviinit;
|
||||
|
||||
DROP PROCEDURE IF EXISTS ravireset;
|
||||
|
||||
ALTER TABLE IF EXISTS public.normal_shop_items RENAME TO shop_items;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN shoptype TO shop_type;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN shopid TO shop_id;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN itemhash TO id;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN itemid TO item_id;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items ALTER COLUMN points TYPE integer;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN points TO cost;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN tradequantity TO quantity;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN rankreqlow TO min_hr;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN rankreqhigh TO min_sr;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN rankreqg TO min_gr;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN storelevelreq TO store_level;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN maximumquantity TO max_quantity;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items DROP COLUMN IF EXISTS boughtquantity;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN roadfloorsrequired TO road_floors;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items RENAME COLUMN weeklyfataliskills TO road_fatalis;
|
||||
|
||||
ALTER TABLE public.shop_items RENAME CONSTRAINT normal_shop_items_pkey TO shop_items_pkey;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items DROP CONSTRAINT IF EXISTS normal_shop_items_itemhash_key;
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.shop_items_id_seq;
|
||||
|
||||
ALTER SEQUENCE IF EXISTS public.shop_items_id_seq OWNER TO postgres;
|
||||
|
||||
ALTER TABLE IF EXISTS public.shop_items ALTER COLUMN id SET DEFAULT nextval('shop_items_id_seq'::regclass);
|
||||
|
||||
ALTER SEQUENCE IF EXISTS public.shop_items_id_seq OWNED BY shop_items.id;
|
||||
|
||||
SELECT setval('shop_items_id_seq', (SELECT MAX(id) FROM public.shop_items));
|
||||
|
||||
DROP TABLE IF EXISTS public.shop_item_state;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.shop_items_bought (
|
||||
character_id INTEGER,
|
||||
shop_item_id INTEGER,
|
||||
bought INTEGER
|
||||
);
|
||||
|
||||
UPDATE users SET rights = rights-2;
|
||||
|
||||
ALTER TABLE IF EXISTS public.users ALTER COLUMN rights SET DEFAULT 12;
|
||||
|
||||
END;
|
||||
@@ -32,7 +32,7 @@ func stubEnumerateNoResults(s *Session, ackHandle uint32) {
|
||||
|
||||
func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(uint32(s.server.erupeConfig.DevModeOptions.EarthIDOverride))
|
||||
bf.WriteUint32(uint32(s.server.erupeConfig.EarthID))
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(uint32(len(data)))
|
||||
@@ -128,7 +128,7 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysLogin)
|
||||
|
||||
if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck {
|
||||
if !s.server.erupeConfig.DebugOptions.DisableTokenCheck {
|
||||
var token string
|
||||
err := s.server.db.QueryRow("SELECT token FROM sign_sessions ss INNER JOIN public.users u on ss.user_id = u.id WHERE token=$1 AND ss.id=$2 AND u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.LoginTokenString, pkt.LoginTokenNumber, pkt.CharID0).Scan(&token)
|
||||
if err != nil {
|
||||
@@ -1148,9 +1148,9 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start
|
||||
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
|
||||
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride)
|
||||
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride)
|
||||
for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride {
|
||||
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
|
||||
|
||||
@@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint32(cafeTime) // Total cafe time
|
||||
bf.WriteUint16(0)
|
||||
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
|
||||
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/mhfcid"
|
||||
"erupe-ce/common/mhfcourse"
|
||||
"erupe-ce/common/token"
|
||||
"erupe-ce/config"
|
||||
@@ -58,7 +60,7 @@ func init() {
|
||||
}
|
||||
|
||||
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
|
||||
}
|
||||
|
||||
func sendServerChatMessage(s *Session, message string) {
|
||||
@@ -86,28 +88,97 @@ func sendServerChatMessage(s *Session, message string) {
|
||||
func parseChatCommand(s *Session, command string) {
|
||||
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
|
||||
switch args[0] {
|
||||
case commands["Ban"].Prefix:
|
||||
if s.isOp() {
|
||||
if len(args) > 1 {
|
||||
var expiry time.Time
|
||||
if len(args) > 2 {
|
||||
var length int
|
||||
var unit string
|
||||
n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit)
|
||||
if err == nil && n == 2 {
|
||||
switch unit {
|
||||
case "s", "second", "seconds":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Second)
|
||||
case "m", "mi", "minute", "minutes":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Minute)
|
||||
case "h", "hour", "hours":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour)
|
||||
case "d", "day", "days":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24)
|
||||
case "mo", "month", "months":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30)
|
||||
case "y", "year", "years":
|
||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
||||
return
|
||||
}
|
||||
}
|
||||
cid := mhfcid.ConvertCID(args[1])
|
||||
if cid > 0 {
|
||||
var uid uint32
|
||||
var uname string
|
||||
err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname)
|
||||
if err == nil {
|
||||
if expiry.IsZero() {
|
||||
s.server.db.Exec(`INSERT INTO bans VALUES ($1)
|
||||
ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid)
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname))
|
||||
} else {
|
||||
s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2)
|
||||
ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry)
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime)))
|
||||
}
|
||||
s.server.DisconnectUser(uid)
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.noUser)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.invalid)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.noOp)
|
||||
}
|
||||
case commands["Timer"].Prefix:
|
||||
if commands["Timer"].Enabled || s.isOp() {
|
||||
var state bool
|
||||
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state)
|
||||
s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID)
|
||||
if state {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.timer.disabled)
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.timer.enabled)
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Timer"])
|
||||
}
|
||||
case commands["PSN"].Prefix:
|
||||
if commands["PSN"].Enabled {
|
||||
if commands["PSN"].Enabled || s.isOp() {
|
||||
if len(args) > 1 {
|
||||
var exists int
|
||||
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
|
||||
if exists == 0 {
|
||||
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1]))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["PSN"])
|
||||
}
|
||||
case commands["Reload"].Prefix:
|
||||
if commands["Reload"].Enabled {
|
||||
sendServerChatMessage(s, s.server.dict["commandReload"])
|
||||
if commands["Reload"].Enabled || s.isOp() {
|
||||
sendServerChatMessage(s, s.server.i18n.commands.reload)
|
||||
var temp mhfpacket.MHFPacket
|
||||
deleteNotif := byteframe.NewByteFrame()
|
||||
for _, object := range s.stage.objects {
|
||||
@@ -167,21 +238,21 @@ func parseChatCommand(s *Session, command string) {
|
||||
sendDisabledCommandMessage(s, commands["Reload"])
|
||||
}
|
||||
case commands["KeyQuest"].Prefix:
|
||||
if commands["KeyQuest"].Enabled {
|
||||
if commands["KeyQuest"].Enabled || s.isOp() {
|
||||
if s.server.erupeConfig.RealClientMode < _config.G10 {
|
||||
sendServerChatMessage(s, s.server.dict["commandKqfVersion"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
|
||||
} else {
|
||||
if len(args) > 1 {
|
||||
if args[1] == "get" {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, s.kqf))
|
||||
} else if args[1] == "set" {
|
||||
if len(args) > 2 && len(args[2]) == 16 {
|
||||
hexd, _ := hex.DecodeString(args[2])
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,23 +261,23 @@ func parseChatCommand(s *Session, command string) {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
case commands["Rights"].Prefix:
|
||||
if commands["Rights"].Enabled {
|
||||
if commands["Rights"].Enabled || s.isOp() {
|
||||
if len(args) > 1 {
|
||||
v, _ := strconv.Atoi(args[1])
|
||||
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Rights"])
|
||||
}
|
||||
case commands["Course"].Prefix:
|
||||
if commands["Course"].Enabled {
|
||||
if commands["Course"].Enabled || s.isOp() {
|
||||
if len(args) > 1 {
|
||||
for _, course := range mhfcourse.Courses() {
|
||||
for _, alias := range course.Aliases() {
|
||||
@@ -224,11 +295,11 @@ func parseChatCommand(s *Session, command string) {
|
||||
})
|
||||
if ei != -1 {
|
||||
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0]))
|
||||
}
|
||||
} else {
|
||||
delta = uint32(math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0]))
|
||||
}
|
||||
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
|
||||
if err == nil {
|
||||
@@ -236,71 +307,71 @@ func parseChatCommand(s *Session, command string) {
|
||||
}
|
||||
updateRights(s)
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0]))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0]))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Course"])
|
||||
}
|
||||
case commands["Raviente"].Prefix:
|
||||
if commands["Raviente"].Enabled {
|
||||
if commands["Raviente"].Enabled || s.isOp() {
|
||||
if len(args) > 1 {
|
||||
if s.server.getRaviSemaphore() != nil {
|
||||
switch args[1] {
|
||||
case "start":
|
||||
if s.server.raviente.register[1] == 0 {
|
||||
s.server.raviente.register[1] = s.server.raviente.register[3]
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success)
|
||||
s.notifyRavi()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
|
||||
}
|
||||
case "cm", "check", "checkmultiplier", "multiplier":
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier()))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, s.server.GetRaviMultiplier()))
|
||||
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
|
||||
if s.server.erupeConfig.RealClientMode == _config.ZZ {
|
||||
switch args[1] {
|
||||
case "sr", "sendres", "resurrection":
|
||||
if s.server.raviente.state[28] > 0 {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.success)
|
||||
s.server.raviente.state[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
|
||||
}
|
||||
case "ss", "sendsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP
|
||||
case "rs", "reqsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.request)
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP + 1
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
|
||||
}
|
||||
default:
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||
}
|
||||
case commands["Teleport"].Prefix:
|
||||
if commands["Teleport"].Enabled {
|
||||
if commands["Teleport"].Enabled || s.isOp() {
|
||||
if len(args) > 2 {
|
||||
x, _ := strconv.ParseInt(args[1], 10, 16)
|
||||
y, _ := strconv.ParseInt(args[2], 10, 16)
|
||||
@@ -315,17 +386,31 @@ func parseChatCommand(s *Session, command string) {
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||
}
|
||||
case commands["Discord"].Prefix:
|
||||
if commands["Discord"].Enabled || s.isOp() {
|
||||
var _token string
|
||||
err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token)
|
||||
if err != nil {
|
||||
randToken := make([]byte, 4)
|
||||
rand.Read(randToken)
|
||||
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:])
|
||||
s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID)
|
||||
}
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token))
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Discord"])
|
||||
}
|
||||
case commands["Help"].Prefix:
|
||||
if commands["Help"].Enabled {
|
||||
if commands["Help"].Enabled || s.isOp() {
|
||||
for _, command := range commands {
|
||||
if command.Enabled {
|
||||
if command.Enabled || s.isOp() {
|
||||
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
|
||||
}
|
||||
}
|
||||
@@ -341,14 +426,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
|
||||
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
|
||||
_ = tmp.ReadBytes(9)
|
||||
tmp.SetLE()
|
||||
frame := tmp.ReadUint32()
|
||||
sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
|
||||
var timer bool
|
||||
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer)
|
||||
if timer {
|
||||
_ = tmp.ReadBytes(9)
|
||||
tmp.SetLE()
|
||||
frame := tmp.ReadUint32()
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.timer, frame/30/60/60, frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools == true && s.server.erupeConfig.DevMode {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
|
||||
// This is only correct most of the time
|
||||
tmp.ReadBytes(20)
|
||||
@@ -362,24 +451,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Parse out the real casted binary payload
|
||||
var msgBinTargeted *binpacket.MsgBinTargeted
|
||||
var authorLen, msgLen uint16
|
||||
var msg []byte
|
||||
|
||||
isDiceCommand := false
|
||||
var message, author string
|
||||
var returnToSender bool
|
||||
if pkt.MessageType == BinaryMessageTypeChat {
|
||||
tmp.SetLE()
|
||||
tmp.Seek(int64(0), 0)
|
||||
_ = tmp.ReadUint32()
|
||||
authorLen = tmp.ReadUint16()
|
||||
msgLen = tmp.ReadUint16()
|
||||
msg = tmp.ReadNullTerminatedBytes()
|
||||
tmp.Seek(8, 0)
|
||||
message = string(tmp.ReadNullTerminatedBytes())
|
||||
author = string(tmp.ReadNullTerminatedBytes())
|
||||
}
|
||||
|
||||
// Customise payload
|
||||
realPayload := pkt.RawDataPayload
|
||||
if pkt.BroadcastType == BroadcastTypeTargeted {
|
||||
tmp.SetBE()
|
||||
tmp.Seek(int64(0), 0)
|
||||
tmp.Seek(0, 0)
|
||||
msgBinTargeted = &binpacket.MsgBinTargeted{}
|
||||
err := msgBinTargeted.Parse(tmp)
|
||||
if err != nil {
|
||||
@@ -388,18 +473,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
realPayload = msgBinTargeted.RawDataPayload
|
||||
} else if pkt.MessageType == BinaryMessageTypeChat {
|
||||
if msgLen == 6 && string(msg) == "@dice" {
|
||||
isDiceCommand = true
|
||||
roll := byteframe.NewByteFrame()
|
||||
roll.WriteInt16(1) // Unk
|
||||
roll.SetLE()
|
||||
roll.WriteUint16(4) // Unk
|
||||
roll.WriteUint16(authorLen)
|
||||
dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1)
|
||||
roll.WriteUint16(uint16(len(dice) + 1))
|
||||
roll.WriteNullTerminatedBytes([]byte(dice))
|
||||
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
|
||||
realPayload = roll.Data()
|
||||
if message == "@dice" {
|
||||
returnToSender = true
|
||||
m := binpacket.MsgBinChat{
|
||||
Type: BinaryMessageTypeChat,
|
||||
Flags: 4,
|
||||
Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1),
|
||||
SenderName: author,
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.SetLE()
|
||||
m.Build(bf)
|
||||
realPayload = bf.Data()
|
||||
} else {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf.SetLE()
|
||||
@@ -428,8 +513,8 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
case BroadcastTypeWorld:
|
||||
s.server.WorldcastMHF(resp, s, nil)
|
||||
case BroadcastTypeStage:
|
||||
if isDiceCommand {
|
||||
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
|
||||
if returnToSender {
|
||||
s.stage.BroadcastMHF(resp, nil)
|
||||
} else {
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
|
||||
@@ -97,6 +97,17 @@ func getPointers() map[SavePointer]int {
|
||||
pointers[pGalleryData] = 72064
|
||||
pointers[pGardenData] = 74424
|
||||
pointers[pRP] = 74614
|
||||
case _config.S6:
|
||||
pointers[pWeaponID] = 12522
|
||||
pointers[pWeaponType] = 12789
|
||||
pointers[pHouseTier] = 13900
|
||||
pointers[pToreData] = 14228
|
||||
pointers[pHRP] = 14550
|
||||
pointers[pHouseData] = 14561
|
||||
pointers[pBookshelfData] = 9118 // Probably same here
|
||||
pointers[pGalleryData] = 24064
|
||||
pointers[pGardenData] = 26424
|
||||
pointers[pRP] = 26614
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode == _config.G5 {
|
||||
pointers[lBookshelfData] = 5548
|
||||
@@ -212,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
|
||||
save.Gender = false
|
||||
}
|
||||
if !save.IsNewCharacter {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.F4 {
|
||||
if _config.ErupeConfig.RealClientMode >= _config.S6 {
|
||||
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2])
|
||||
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
|
||||
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/mhfmon"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
@@ -45,7 +46,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if s.server.erupeConfig.DevModeOptions.SaveDumps.RawEnabled {
|
||||
if s.server.erupeConfig.SaveDumps.RawEnabled {
|
||||
dumpSaveData(s, saveData, "raw-savedata")
|
||||
}
|
||||
s.logger.Info("Updating save with blob")
|
||||
@@ -112,11 +113,11 @@ func grpToGR(n int) uint16 {
|
||||
}
|
||||
|
||||
func dumpSaveData(s *Session, data []byte, suffix string) {
|
||||
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
|
||||
if !s.server.erupeConfig.SaveDumps.Enabled {
|
||||
return
|
||||
} else {
|
||||
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
|
||||
path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
|
||||
dir := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID))
|
||||
path := filepath.Join(s.server.erupeConfig.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID), fmt.Sprintf("%d_%s.bin", s.charID, suffix))
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -1042,34 +1043,34 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) {
|
||||
{1105, 1, 10, 500, 0, 0, 0},
|
||||
{1105, 2, 10, 500, 0, 0, 0},
|
||||
// setServerBoss
|
||||
{2001, 1, 17, 58, 0, 6, 700},
|
||||
{2001, 1, 20, 58, 0, 3, 200},
|
||||
{2001, 1, 22, 58, 0, 7, 250},
|
||||
{2001, 1, 27, 58, 0, 1, 100},
|
||||
{2001, 1, 53, 58, 0, 8, 1000},
|
||||
{2001, 1, 67, 58, 0, 9, 500},
|
||||
{2001, 1, 68, 58, 0, 2, 150},
|
||||
{2001, 1, 74, 58, 0, 4, 200},
|
||||
{2001, 1, 75, 58, 0, 5, 500},
|
||||
{2001, 1, 76, 58, 0, 10, 800},
|
||||
{2001, 1, 80, 58, 0, 11, 900},
|
||||
{2001, 1, 89, 58, 0, 12, 600},
|
||||
{2001, 2, 17, 60, 0, 6, 700},
|
||||
{2001, 2, 20, 60, 0, 3, 200},
|
||||
{2001, 2, 22, 60, 0, 7, 350},
|
||||
{2001, 2, 27, 60, 0, 1, 100},
|
||||
{2001, 2, 39, 60, 0, 13, 200},
|
||||
{2001, 2, 40, 60, 0, 15, 600},
|
||||
{2001, 2, 53, 60, 0, 8, 1000},
|
||||
{2001, 2, 67, 60, 0, 2, 500},
|
||||
{2001, 2, 68, 60, 0, 9, 150},
|
||||
{2001, 2, 74, 60, 0, 4, 200},
|
||||
{2001, 2, 75, 60, 0, 5, 500},
|
||||
{2001, 2, 76, 60, 0, 10, 800},
|
||||
{2001, 2, 80, 60, 0, 11, 900},
|
||||
{2001, 2, 81, 60, 0, 14, 900},
|
||||
{2001, 2, 89, 60, 0, 12, 600},
|
||||
{2001, 2, 94, 60, 0, 16, 1000},
|
||||
{2001, 1, mhfmon.Gravios, 58, 0, 6, 700},
|
||||
{2001, 1, mhfmon.Gypceros, 58, 0, 3, 200},
|
||||
{2001, 1, mhfmon.Basarios, 58, 0, 7, 250},
|
||||
{2001, 1, mhfmon.Velocidrome, 58, 0, 1, 100},
|
||||
{2001, 1, mhfmon.Rajang, 58, 0, 8, 1000},
|
||||
{2001, 1, mhfmon.ShogunCeanataur, 58, 0, 9, 500},
|
||||
{2001, 1, mhfmon.Bulldrome, 58, 0, 2, 150},
|
||||
{2001, 1, mhfmon.Hypnocatrice, 58, 0, 4, 200},
|
||||
{2001, 1, mhfmon.Lavasioth, 58, 0, 5, 500},
|
||||
{2001, 1, mhfmon.Tigrex, 58, 0, 10, 800},
|
||||
{2001, 1, mhfmon.Espinas, 58, 0, 11, 900},
|
||||
{2001, 1, mhfmon.Pariapuria, 58, 0, 12, 600},
|
||||
{2001, 2, mhfmon.Gravios, 60, 0, 6, 700},
|
||||
{2001, 2, mhfmon.Gypceros, 60, 0, 3, 200},
|
||||
{2001, 2, mhfmon.Basarios, 60, 0, 7, 350},
|
||||
{2001, 2, mhfmon.Velocidrome, 60, 0, 1, 100},
|
||||
{2001, 2, mhfmon.PurpleGypceros, 60, 0, 13, 200},
|
||||
{2001, 2, mhfmon.YianGaruga, 60, 0, 15, 600},
|
||||
{2001, 2, mhfmon.Rajang, 60, 0, 8, 1000},
|
||||
{2001, 2, mhfmon.ShogunCeanataur, 60, 0, 2, 500},
|
||||
{2001, 2, mhfmon.Bulldrome, 60, 0, 9, 150},
|
||||
{2001, 2, mhfmon.Hypnocatrice, 60, 0, 4, 200},
|
||||
{2001, 2, mhfmon.Lavasioth, 60, 0, 5, 500},
|
||||
{2001, 2, mhfmon.Tigrex, 60, 0, 10, 800},
|
||||
{2001, 2, mhfmon.Espinas, 60, 0, 11, 900},
|
||||
{2001, 2, mhfmon.BurningEspinas, 60, 0, 14, 900},
|
||||
{2001, 2, mhfmon.Pariapuria, 60, 0, 12, 600},
|
||||
{2001, 2, mhfmon.Dyuragaua, 60, 0, 16, 1000},
|
||||
}
|
||||
case 6:
|
||||
paperData = []PaperData{
|
||||
|
||||
@@ -3,6 +3,7 @@ package channelserver
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -66,10 +67,56 @@ func getCharacterList(s *Server) string {
|
||||
return message
|
||||
}
|
||||
|
||||
// onInteraction handles slash commands
|
||||
func (s *Server) onInteraction(ds *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
switch i.Interaction.ApplicationCommandData().Name {
|
||||
case "link":
|
||||
var temp string
|
||||
err := s.db.QueryRow(`UPDATE users SET discord_id = $1 WHERE discord_token = $2 RETURNING discord_id`, i.Member.User.ID, i.ApplicationCommandData().Options[0].StringValue()).Scan(&temp)
|
||||
if err == nil {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Your Erupe account was linked successfully.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Failed to link Erupe account.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
}
|
||||
case "password":
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte(i.ApplicationCommandData().Options[0].StringValue()), 10)
|
||||
_, err := s.db.Exec(`UPDATE users SET password = $1 WHERE discord_id = $2`, password, i.Member.User.ID)
|
||||
if err == nil {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Your Erupe account password has been updated.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ds.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Failed to update Erupe account password.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// onDiscordMessage handles receiving messages from discord and forwarding them ingame.
|
||||
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
// Ignore messages from our bot, or ones that are not in the correct channel.
|
||||
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
|
||||
// Ignore messages from bots, or messages that are not in the correct channel.
|
||||
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RelayChannel.RelayChannelID {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,11 +126,24 @@ func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCre
|
||||
}
|
||||
return r
|
||||
}, m.Author.Username))
|
||||
|
||||
for i := 0; i < 8-len(m.Author.Username); i++ {
|
||||
paddedName += " "
|
||||
}
|
||||
message := s.discordBot.NormalizeDiscordMessage(fmt.Sprintf("[D] %s > %s", paddedName, m.Content))
|
||||
if len(message) > s.erupeConfig.Discord.RelayChannel.MaxMessageLength {
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
|
||||
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
|
||||
var messages []string
|
||||
lineLength := 61
|
||||
for i := 0; i < len(message); i += lineLength {
|
||||
end := i + lineLength
|
||||
if end > len(message) {
|
||||
end = len(message)
|
||||
}
|
||||
messages = append(messages, message[i:end])
|
||||
}
|
||||
for i := range messages {
|
||||
s.BroadcastChatMessage(messages[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
|
||||
if s.server.erupeConfig.DebugOptions.DivaOverride >= 0 {
|
||||
if s.server.erupeConfig.DebugOptions.DivaOverride == 0 {
|
||||
if s.server.erupeConfig.RealClientMode >= _config.Z2 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
|
||||
} else {
|
||||
@@ -79,7 +79,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
return
|
||||
}
|
||||
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
|
||||
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaOverride), true)
|
||||
} else {
|
||||
timestamps = generateDivaTimestamps(s, start, false)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
state := s.server.erupeConfig.DevModeOptions.TournamentEvent
|
||||
state := s.server.erupeConfig.DebugOptions.TournamentOverride
|
||||
// Unk
|
||||
// Unk
|
||||
// Start?
|
||||
@@ -95,8 +95,9 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
func cleanupFesta(s *Session) {
|
||||
s.server.db.Exec("DELETE FROM events WHERE event_type='festa'")
|
||||
s.server.db.Exec("DELETE FROM festa_registrations")
|
||||
s.server.db.Exec("DELETE FROM festa_submissions")
|
||||
s.server.db.Exec("DELETE FROM festa_prizes_accepted")
|
||||
s.server.db.Exec("UPDATE guild_characters SET souls=0, trial_vote=NULL")
|
||||
s.server.db.Exec("UPDATE guild_characters SET trial_vote=NULL")
|
||||
}
|
||||
|
||||
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
@@ -141,13 +142,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
}
|
||||
|
||||
type FestaTrial struct {
|
||||
ID uint32 `db:"id"`
|
||||
Objective uint16 `db:"objective"`
|
||||
GoalID uint32 `db:"goal_id"`
|
||||
TimesReq uint16 `db:"times_req"`
|
||||
Locale uint16 `db:"locale_req"`
|
||||
Reward uint16 `db:"reward"`
|
||||
Monopoly FestivalColour `db:"monopoly"`
|
||||
ID uint32 `db:"id"`
|
||||
Objective uint16 `db:"objective"`
|
||||
GoalID uint32 `db:"goal_id"`
|
||||
TimesReq uint16 `db:"times_req"`
|
||||
Locale uint16 `db:"locale_req"`
|
||||
Reward uint16 `db:"reward"`
|
||||
Monopoly FestivalColor `db:"monopoly"`
|
||||
Unk uint16
|
||||
}
|
||||
|
||||
@@ -173,12 +174,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
|
||||
if s.server.erupeConfig.DebugOptions.FestaOverride >= 0 {
|
||||
if s.server.erupeConfig.DebugOptions.FestaOverride == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
|
||||
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaOverride), true)
|
||||
} else {
|
||||
timestamps = generateFestaTimestamps(s, start, false)
|
||||
}
|
||||
@@ -233,8 +234,10 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(trial.TimesReq)
|
||||
bf.WriteUint16(trial.Locale)
|
||||
bf.WriteUint16(trial.Reward)
|
||||
bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly]))
|
||||
bf.WriteUint16(trial.Unk)
|
||||
bf.WriteInt16(FestivalColorCodes[trial.Monopoly])
|
||||
if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0
|
||||
bf.WriteUint16(trial.Unk)
|
||||
}
|
||||
}
|
||||
|
||||
// The Winner and Loser Armor IDs are missing
|
||||
@@ -291,26 +294,49 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint16(500)
|
||||
|
||||
categoryWinners := uint16(0) // NYI
|
||||
bf.WriteUint16(categoryWinners)
|
||||
for i := uint16(0); i < categoryWinners; i++ {
|
||||
bf.WriteUint32(0) // Guild ID
|
||||
bf.WriteUint16(i + 1) // Category ID
|
||||
bf.WriteUint16(0) // Festa Team
|
||||
ps.Uint8(bf, "", true) // Guild Name
|
||||
}
|
||||
|
||||
dailyWinners := uint16(0) // NYI
|
||||
bf.WriteUint16(dailyWinners)
|
||||
for i := uint16(0); i < dailyWinners; i++ {
|
||||
bf.WriteUint32(0) // Guild ID
|
||||
bf.WriteUint16(i + 1) // Category ID
|
||||
bf.WriteUint16(0) // Festa Team
|
||||
ps.Uint8(bf, "", true) // Guild Name
|
||||
var temp uint32
|
||||
bf.WriteUint16(4)
|
||||
for i := uint16(0); i < 4; i++ {
|
||||
var guildID uint32
|
||||
var guildName string
|
||||
var guildTeam = FestivalColorNone
|
||||
s.server.db.QueryRow(`
|
||||
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
|
||||
FROM festa_submissions fs
|
||||
LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
|
||||
LEFT JOIN guilds g ON fs.guild_id = g.id
|
||||
WHERE fs.trial_type = $1
|
||||
GROUP BY fs.guild_id, g.name, fr.team
|
||||
ORDER BY _ DESC LIMIT 1
|
||||
`, i+1).Scan(&guildID, &guildName, &guildTeam, &temp)
|
||||
bf.WriteUint32(guildID)
|
||||
bf.WriteUint16(i + 1)
|
||||
bf.WriteInt16(FestivalColorCodes[guildTeam])
|
||||
ps.Uint8(bf, guildName, true)
|
||||
}
|
||||
bf.WriteUint16(7)
|
||||
for i := uint16(0); i < 7; i++ {
|
||||
var guildID uint32
|
||||
var guildName string
|
||||
var guildTeam = FestivalColorNone
|
||||
offset := 86400 * uint32(i)
|
||||
s.server.db.QueryRow(`
|
||||
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
|
||||
FROM festa_submissions fs
|
||||
LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
|
||||
LEFT JOIN guilds g ON fs.guild_id = g.id
|
||||
WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2
|
||||
GROUP BY fs.guild_id, g.name, fr.team
|
||||
ORDER BY _ DESC LIMIT 1
|
||||
`, timestamps[1]+offset, timestamps[1]+offset+86400).Scan(&guildID, &guildName, &guildTeam, &temp)
|
||||
bf.WriteUint32(guildID)
|
||||
bf.WriteUint16(i + 1)
|
||||
bf.WriteInt16(FestivalColorCodes[guildTeam])
|
||||
ps.Uint8(bf, guildName, true)
|
||||
}
|
||||
|
||||
bf.WriteUint32(0) // Clan goal
|
||||
// Final bonus rates
|
||||
bf.WriteUint32(1) // 5000-Infinity?
|
||||
bf.WriteUint32(5000) // 5000+ souls
|
||||
bf.WriteUint32(2000) // 2000-4999 souls
|
||||
bf.WriteUint32(1000) // 1000-1999 souls
|
||||
@@ -321,7 +347,9 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(100) // Normal rate
|
||||
bf.WriteUint16(50) // 50% penalty
|
||||
|
||||
ps.Uint16(bf, "", false)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G52 {
|
||||
ps.Uint16(bf, "", false)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -349,7 +377,6 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBool(true)
|
||||
}
|
||||
bf.WriteUint16(0) // Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -364,18 +391,18 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
||||
resp := byteframe.NewByteFrame()
|
||||
if err != nil || guild == nil || applicant {
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0xFFFFFFFF)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteInt32(0)
|
||||
resp.WriteInt32(-1)
|
||||
resp.WriteInt32(0)
|
||||
resp.WriteInt32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
return
|
||||
}
|
||||
resp.WriteUint32(guild.Souls)
|
||||
resp.WriteUint32(1) // unk
|
||||
resp.WriteUint32(1) // unk
|
||||
resp.WriteUint32(1) // unk, rank?
|
||||
resp.WriteUint32(1) // unk
|
||||
resp.WriteInt32(1) // unk
|
||||
resp.WriteInt32(1) // unk, rank?
|
||||
resp.WriteInt32(1) // unk
|
||||
resp.WriteInt32(1) // unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
@@ -391,15 +418,26 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(members)))
|
||||
bf.WriteUint16(0) // Unk
|
||||
sort.Slice(members, func(i, j int) bool {
|
||||
return members[i].Souls > members[j].Souls
|
||||
})
|
||||
var validMembers []*GuildMember
|
||||
for _, member := range members {
|
||||
if member.Souls > 0 {
|
||||
validMembers = append(validMembers, member)
|
||||
}
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(validMembers)))
|
||||
bf.WriteUint16(0) // Unk
|
||||
for _, member := range validMembers {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteUint32(member.Souls)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
|
||||
bf.WriteUint16(uint16(member.Souls))
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
bf.WriteUint32(member.Souls)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
@@ -431,7 +469,14 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
|
||||
s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID)
|
||||
tx, _ := s.server.db.Begin()
|
||||
for i := range pkt.Souls {
|
||||
if pkt.Souls[i] == 0 {
|
||||
continue
|
||||
}
|
||||
_, _ = tx.Exec(`INSERT INTO festa_submissions VALUES ($1, $2, $3, $4, now())`, s.charID, pkt.GuildID, i, pkt.Souls[i])
|
||||
}
|
||||
_ = tx.Commit()
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
|
||||
@@ -21,18 +21,18 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type FestivalColour string
|
||||
type FestivalColor string
|
||||
|
||||
const (
|
||||
FestivalColourNone FestivalColour = "none"
|
||||
FestivalColourBlue FestivalColour = "blue"
|
||||
FestivalColourRed FestivalColour = "red"
|
||||
FestivalColorNone FestivalColor = "none"
|
||||
FestivalColorBlue FestivalColor = "blue"
|
||||
FestivalColorRed FestivalColor = "red"
|
||||
)
|
||||
|
||||
var FestivalColourCodes = map[FestivalColour]int8{
|
||||
FestivalColourNone: -1,
|
||||
FestivalColourBlue: 0,
|
||||
FestivalColourRed: 1,
|
||||
var FestivalColorCodes = map[FestivalColor]int16{
|
||||
FestivalColorNone: -1,
|
||||
FestivalColorBlue: 0,
|
||||
FestivalColorRed: 1,
|
||||
}
|
||||
|
||||
type GuildApplicationType string
|
||||
@@ -43,27 +43,27 @@ const (
|
||||
)
|
||||
|
||||
type Guild struct {
|
||||
ID uint32 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
MainMotto uint8 `db:"main_motto"`
|
||||
SubMotto uint8 `db:"sub_motto"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
MemberCount uint16 `db:"member_count"`
|
||||
RankRP uint32 `db:"rank_rp"`
|
||||
EventRP uint32 `db:"event_rp"`
|
||||
Comment string `db:"comment"`
|
||||
PugiName1 string `db:"pugi_name_1"`
|
||||
PugiName2 string `db:"pugi_name_2"`
|
||||
PugiName3 string `db:"pugi_name_3"`
|
||||
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
|
||||
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
|
||||
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
|
||||
PugiOutfits uint32 `db:"pugi_outfits"`
|
||||
Recruiting bool `db:"recruiting"`
|
||||
FestivalColour FestivalColour `db:"festival_colour"`
|
||||
Souls uint32 `db:"souls"`
|
||||
AllianceID uint32 `db:"alliance_id"`
|
||||
Icon *GuildIcon `db:"icon"`
|
||||
ID uint32 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
MainMotto uint8 `db:"main_motto"`
|
||||
SubMotto uint8 `db:"sub_motto"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
MemberCount uint16 `db:"member_count"`
|
||||
RankRP uint32 `db:"rank_rp"`
|
||||
EventRP uint32 `db:"event_rp"`
|
||||
Comment string `db:"comment"`
|
||||
PugiName1 string `db:"pugi_name_1"`
|
||||
PugiName2 string `db:"pugi_name_2"`
|
||||
PugiName3 string `db:"pugi_name_3"`
|
||||
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
|
||||
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
|
||||
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
|
||||
PugiOutfits uint32 `db:"pugi_outfits"`
|
||||
Recruiting bool `db:"recruiting"`
|
||||
FestivalColor FestivalColor `db:"festival_colour"`
|
||||
Souls uint32 `db:"souls"`
|
||||
AllianceID uint32 `db:"alliance_id"`
|
||||
Icon *GuildIcon `db:"icon"`
|
||||
|
||||
GuildLeader
|
||||
}
|
||||
@@ -967,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(uint8(len(guildLeaderName)))
|
||||
bf.WriteBytes(guildName)
|
||||
bf.WriteBytes(guildComment)
|
||||
bf.WriteInt8(FestivalColourCodes[guild.FestivalColour])
|
||||
bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor]))
|
||||
bf.WriteUint32(guild.RankRP)
|
||||
bf.WriteBytes(guildLeaderName)
|
||||
bf.WriteUint32(0) // Unk
|
||||
@@ -1427,7 +1427,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
bf.WriteUint16(uint16(len(guildMembers)))
|
||||
|
||||
sort.Slice(guildMembers[:], func(i, j int) bool {
|
||||
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
|
||||
@@ -1460,7 +1460,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
if guild.AllianceID > 0 {
|
||||
bf.WriteUint16(alliance.TotalMembers - guild.MemberCount)
|
||||
bf.WriteUint16(alliance.TotalMembers - uint16(len(guildMembers)))
|
||||
if guild.ID != alliance.ParentGuildID {
|
||||
mems, err := GetGuildMembers(s, alliance.ParentGuildID, false)
|
||||
if err != nil {
|
||||
|
||||
@@ -61,41 +61,35 @@ func (gm *GuildMember) Save(s *Session) error {
|
||||
}
|
||||
|
||||
const guildMembersSelectSQL = `
|
||||
SELECT
|
||||
g.id as guild_id,
|
||||
joined_at,
|
||||
coalesce(souls, 0) as souls,
|
||||
COALESCE(rp_today, 0) AS rp_today,
|
||||
COALESCE(rp_yesterday, 0) AS rp_yesterday,
|
||||
c.name,
|
||||
character.character_id,
|
||||
coalesce(gc.order_index, 0) as order_index,
|
||||
c.last_login,
|
||||
coalesce(gc.recruiter, false) as recruiter,
|
||||
coalesce(gc.avoid_leadership, false) as avoid_leadership,
|
||||
c.hrp,
|
||||
c.gr,
|
||||
c.weapon_id,
|
||||
c.weapon_type,
|
||||
character.is_applicant,
|
||||
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 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
|
||||
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.hrp,
|
||||
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
|
||||
FROM guild_characters gc
|
||||
) character
|
||||
JOIN characters c on character.character_id = c.id
|
||||
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
|
||||
JOIN guilds g ON g.id = character.guild_id
|
||||
LEFT JOIN characters c ON c.id = gc.character_id
|
||||
LEFT JOIN guilds g ON g.id = gc.guild_id
|
||||
) AS subquery
|
||||
`
|
||||
|
||||
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE character.guild_id = $1 AND is_applicant = $2
|
||||
WHERE guild_id = $1 AND is_applicant = $2
|
||||
`, guildMembersSelectSQL), guildID, applicants)
|
||||
|
||||
if err != nil {
|
||||
@@ -121,7 +115,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.character_id=$1", guildMembersSelectSQL), charID)
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))
|
||||
|
||||
@@ -60,9 +60,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
mail := &Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: s.server.dict["guildInviteName"],
|
||||
Subject: s.server.i18n.guild.invite.title,
|
||||
Body: fmt.Sprintf(
|
||||
s.server.dict["guildInvite"],
|
||||
s.server.i18n.guild.invite.body,
|
||||
guildInfo.Name,
|
||||
),
|
||||
IsGuildInvite: true,
|
||||
@@ -146,30 +146,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
err = guild.AcceptApplication(s, s.charID)
|
||||
mail = append(mail, Mail{
|
||||
RecipientID: s.charID,
|
||||
Subject: s.server.dict["guildInviteSuccessName"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name),
|
||||
Subject: s.server.i18n.guild.invite.success.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
mail = append(mail, Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.LeaderID,
|
||||
Subject: s.server.dict["guildInviteAcceptedName"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name),
|
||||
Subject: s.server.i18n.guild.invite.accepted.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
} else {
|
||||
err = guild.RejectApplication(s, s.charID)
|
||||
mail = append(mail, Mail{
|
||||
RecipientID: s.charID,
|
||||
Subject: s.server.dict["guildInviteRejectName"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name),
|
||||
Subject: s.server.i18n.guild.invite.rejected.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
mail = append(mail, Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.LeaderID,
|
||||
Subject: s.server.dict["guildInviteDeclined"],
|
||||
Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name),
|
||||
Subject: s.server.i18n.guild.invite.declined.title,
|
||||
Body: fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name),
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysPositionObject)
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("[%s] with objectID [%d] move to (%f,%f,%f)\n\n", s.Name, pkt.ObjID, pkt.X, pkt.Y, pkt.Z)
|
||||
}
|
||||
s.stage.Lock()
|
||||
|
||||
@@ -2,6 +2,7 @@ package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/decryption"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
@@ -16,11 +17,82 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type tuneValue struct {
|
||||
ID uint16
|
||||
Value uint16
|
||||
}
|
||||
|
||||
func findSubSliceIndices(data []byte, sub []byte) []int {
|
||||
var indices []int
|
||||
lenSub := len(sub)
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i+lenSub > len(data) {
|
||||
break
|
||||
}
|
||||
if equal(data[i:i+lenSub], sub) {
|
||||
indices = append(indices, i)
|
||||
}
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
func equal(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func BackportQuest(data []byte) []byte {
|
||||
wp := binary.LittleEndian.Uint32(data[0:4]) + 96
|
||||
rp := wp + 4
|
||||
for i := uint32(0); i < 6; i++ {
|
||||
if i != 0 {
|
||||
wp += 4
|
||||
rp += 8
|
||||
}
|
||||
copy(data[wp:wp+4], data[rp:rp+4])
|
||||
}
|
||||
|
||||
fillLength := uint32(108)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
||||
fillLength = 44
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
fillLength = 52
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
|
||||
fillLength = 76
|
||||
}
|
||||
|
||||
copy(data[wp:wp+fillLength], data[rp:rp+fillLength])
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G91 {
|
||||
patterns := [][]byte{
|
||||
{0x0A, 0x00, 0x01, 0x33, 0xD7, 0x00}, // 10% Armor Sphere -> Stone
|
||||
{0x06, 0x00, 0x02, 0x33, 0xD8, 0x00}, // 6% Armor Sphere+ -> Iron Ore
|
||||
{0x0A, 0x00, 0x03, 0x33, 0xD7, 0x00}, // 10% Adv Armor Sphere -> Stone
|
||||
{0x06, 0x00, 0x04, 0x33, 0xDB, 0x00}, // 6% Hard Armor Sphere -> Dragonite Ore
|
||||
{0x0A, 0x00, 0x05, 0x33, 0xD9, 0x00}, // 10% Heaven Armor Sphere -> Earth Crystal
|
||||
{0x06, 0x00, 0x06, 0x33, 0xDB, 0x00}, // 6% True Armor Sphere -> Dragonite Ore
|
||||
}
|
||||
for i := range patterns {
|
||||
j := findSubSliceIndices(data, patterns[i][0:4])
|
||||
for k := range j {
|
||||
copy(data[j[k]+2:j[k]+4], patterns[i][4:6])
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysGetFile)
|
||||
|
||||
if pkt.IsScenario {
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
"Scenario",
|
||||
zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID),
|
||||
@@ -40,7 +112,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
"Quest",
|
||||
zap.String("Filename", pkt.Filename),
|
||||
@@ -58,6 +130,9 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
return
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
|
||||
data = BackportQuest(decryption.UnpackSimple(data))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
}
|
||||
@@ -119,25 +194,39 @@ func loadQuestFile(s *Session, questId int) []byte {
|
||||
}
|
||||
|
||||
decrypted := decryption.UnpackSimple(file)
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
|
||||
decrypted = BackportQuest(decrypted)
|
||||
}
|
||||
fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
|
||||
fileBytes.SetLE()
|
||||
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
|
||||
|
||||
// The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
|
||||
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320))
|
||||
bodyLength := 320
|
||||
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
||||
bodyLength = 160
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
bodyLength = 168
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
|
||||
bodyLength = 192
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.Z1 {
|
||||
bodyLength = 224
|
||||
}
|
||||
|
||||
// The n bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
|
||||
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(uint(bodyLength)))
|
||||
questBody.SetLE()
|
||||
// Find the master quest string pointer
|
||||
questBody.Seek(40, 0)
|
||||
fileBytes.Seek(int64(questBody.ReadUint32()), 0)
|
||||
questBody.Seek(40, 0)
|
||||
// Overwrite it
|
||||
questBody.WriteUint32(320)
|
||||
questBody.WriteUint32(uint32(bodyLength))
|
||||
questBody.Seek(0, 2)
|
||||
|
||||
// Rewrite the quest strings and their pointers
|
||||
var tempString []byte
|
||||
newStrings := byteframe.NewByteFrame()
|
||||
tempPointer := 352
|
||||
tempPointer := bodyLength + 32
|
||||
for i := 0; i < 8; i++ {
|
||||
questBody.WriteUint32(uint32(tempPointer))
|
||||
temp := int64(fileBytes.Index())
|
||||
@@ -163,7 +252,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
|
||||
|
||||
data := loadQuestFile(s, questId)
|
||||
if data == nil {
|
||||
return nil, fmt.Errorf("failed to load quest file")
|
||||
return nil, fmt.Errorf(fmt.Sprintf("failed to load quest file (%d)", questId))
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
@@ -249,6 +338,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDays, &inactiveDays)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to scan event quest row", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -277,15 +367,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Check if the quest is currently active
|
||||
if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) {
|
||||
break
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
data, err := makeEventQuest(s, rows)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to make event quest", zap.Error(err))
|
||||
continue
|
||||
} else {
|
||||
if len(data) > 896 || len(data) < 352 {
|
||||
s.logger.Error("Invalid quest data length", zap.Int("len", len(data)))
|
||||
continue
|
||||
} else {
|
||||
totalCount++
|
||||
@@ -302,11 +394,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
type tuneValue struct {
|
||||
ID uint16
|
||||
Value uint16
|
||||
}
|
||||
|
||||
tuneValues := []tuneValue{
|
||||
{ID: 20, Value: 1},
|
||||
{ID: 26, Value: 1},
|
||||
@@ -319,43 +406,46 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
{ID: 67, Value: 1},
|
||||
{ID: 80, Value: 1},
|
||||
{ID: 94, Value: 1},
|
||||
{ID: 1010, Value: 300},
|
||||
{ID: 1011, Value: 300},
|
||||
{ID: 1012, Value: 300},
|
||||
{ID: 1013, Value: 300},
|
||||
{ID: 1014, Value: 200},
|
||||
{ID: 1015, Value: 200},
|
||||
{ID: 1021, Value: 400},
|
||||
{ID: 1023, Value: 8},
|
||||
{ID: 1024, Value: 150},
|
||||
{ID: 1025, Value: 1},
|
||||
{ID: 1026, Value: 999}, // get_grank_cap
|
||||
{ID: 1027, Value: 100},
|
||||
{ID: 1028, Value: 100},
|
||||
{ID: 1030, Value: 8},
|
||||
{ID: 1031, Value: 100},
|
||||
{ID: 1032, Value: 0}, // isValid_partner
|
||||
{ID: 1044, Value: 200}, // get_rate_tload_time_out
|
||||
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
|
||||
{ID: 1046, Value: 99},
|
||||
{ID: 1048, Value: 0}, // get_rate_tower_log_disable
|
||||
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
|
||||
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
|
||||
{ID: 1051, Value: 200},
|
||||
{ID: 1052, Value: 200},
|
||||
{ID: 1063, Value: 50000},
|
||||
{ID: 1064, Value: 50000},
|
||||
{ID: 1065, Value: 25000},
|
||||
{ID: 1066, Value: 25000},
|
||||
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1?
|
||||
{ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
|
||||
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
|
||||
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
|
||||
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
|
||||
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
|
||||
{ID: 1078, Value: 0},
|
||||
{ID: 1079, Value: 1},
|
||||
{ID: 1080, Value: 1},
|
||||
{ID: 1001, Value: 100}, // get_hrp_rate
|
||||
{ID: 1010, Value: 300}, // get_hrp_rate_netcafe
|
||||
{ID: 1011, Value: 300}, // get_zeny_rate_netcafe
|
||||
{ID: 1012, Value: 300}, // get_hrp_rate_ncource
|
||||
{ID: 1013, Value: 300}, // get_zeny_rate_ncource
|
||||
{ID: 1014, Value: 200}, // get_hrp_rate_premium
|
||||
{ID: 1015, Value: 200}, // get_zeny_rate_premium
|
||||
{ID: 1021, Value: 400}, // get_gcp_rate_assist
|
||||
{ID: 1023, Value: 8}, // unused?
|
||||
{ID: 1024, Value: 150}, // get_hrp_rate_ptbonus
|
||||
{ID: 1025, Value: 1}, // isValid_stampcard
|
||||
{ID: 1026, Value: 999}, // get_grank_cap
|
||||
{ID: 1027, Value: 100}, // get_exchange_rate_festa
|
||||
{ID: 1028, Value: 100}, // get_exchange_rate_cafe
|
||||
{ID: 1030, Value: 8}, // get_gquest_cap
|
||||
{ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP)
|
||||
{ID: 1032, Value: 0}, // isValid_partner
|
||||
{ID: 1044, Value: 200}, // get_rate_tload_time_out
|
||||
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
|
||||
{ID: 1046, Value: 99}, // get_hunter_life_cap
|
||||
{ID: 1048, Value: 0}, // get_rate_tower_hint_sec
|
||||
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
|
||||
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
|
||||
{ID: 1051, Value: 200}, // get_pallone_score_rate_premium
|
||||
{ID: 1052, Value: 200}, // get_trp_rate_premium
|
||||
{ID: 1063, Value: 50000}, // get_nboost_quest_point_from_hrank
|
||||
{ID: 1064, Value: 50000}, // get_nboost_quest_point_from_srank
|
||||
{ID: 1065, Value: 25000}, // get_nboost_quest_point_from_grank
|
||||
{ID: 1066, Value: 25000}, // get_nboost_quest_point_from_gsrank
|
||||
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1?
|
||||
{ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
|
||||
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
|
||||
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
|
||||
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
|
||||
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
|
||||
{ID: 1078, Value: 0}, // isCapped_tenrou_irai
|
||||
{ID: 1079, Value: 1}, // get_add_tower_level_assist
|
||||
{ID: 1080, Value: 1}, // get_tune_add_tower_level_w_assist_nboost
|
||||
|
||||
// get_tune_secret_book_item
|
||||
{ID: 1081, Value: 1},
|
||||
{ID: 1082, Value: 4},
|
||||
{ID: 1083, Value: 2},
|
||||
@@ -380,14 +470,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
{ID: 1102, Value: 5},
|
||||
{ID: 1103, Value: 2},
|
||||
{ID: 1104, Value: 10},
|
||||
{ID: 1145, Value: 200},
|
||||
{ID: 1146, Value: 0}, // isTower_invisible
|
||||
{ID: 1147, Value: 0}, // isVenom_playable
|
||||
{ID: 1149, Value: 20},
|
||||
{ID: 1152, Value: 1130},
|
||||
{ID: 1154, Value: 0}, // isDisabled_object_season
|
||||
{ID: 1158, Value: 1},
|
||||
{ID: 1160, Value: 300},
|
||||
|
||||
{ID: 1145, Value: 200}, // get_ud_point_rate_premium
|
||||
{ID: 1146, Value: 0}, // isTower_invisible
|
||||
{ID: 1147, Value: 0}, // isVenom_playable
|
||||
{ID: 1149, Value: 20}, // get_ud_break_parts_point
|
||||
{ID: 1152, Value: 1130}, // unused?
|
||||
{ID: 1154, Value: 0}, // isDisabled_object_season
|
||||
{ID: 1158, Value: 1}, // isDelivery_venom_ult_quest
|
||||
{ID: 1160, Value: 300}, // get_rate_premium_ravi_g_enhance_tama
|
||||
|
||||
// unknown
|
||||
{ID: 1162, Value: 1},
|
||||
{ID: 1163, Value: 3},
|
||||
{ID: 1164, Value: 5},
|
||||
@@ -407,240 +500,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
{ID: 1178, Value: 10},
|
||||
{ID: 1179, Value: 2},
|
||||
{ID: 1180, Value: 5},
|
||||
{ID: 3000, Value: 100},
|
||||
{ID: 3001, Value: 100},
|
||||
{ID: 3002, Value: 100},
|
||||
{ID: 3003, Value: 100},
|
||||
{ID: 3004, Value: 100},
|
||||
{ID: 3005, Value: 100},
|
||||
{ID: 3006, Value: 100},
|
||||
{ID: 3007, Value: 100},
|
||||
{ID: 3008, Value: 100},
|
||||
{ID: 3009, Value: 100},
|
||||
{ID: 3010, Value: 100},
|
||||
{ID: 3011, Value: 100},
|
||||
{ID: 3012, Value: 100},
|
||||
{ID: 3013, Value: 100},
|
||||
{ID: 3014, Value: 100},
|
||||
{ID: 3015, Value: 100},
|
||||
{ID: 3016, Value: 100},
|
||||
{ID: 3017, Value: 100},
|
||||
{ID: 3018, Value: 100},
|
||||
{ID: 3019, Value: 100},
|
||||
{ID: 3020, Value: 100},
|
||||
{ID: 3021, Value: 100},
|
||||
{ID: 3022, Value: 100},
|
||||
{ID: 3023, Value: 100},
|
||||
{ID: 3024, Value: 100},
|
||||
{ID: 3025, Value: 100},
|
||||
{ID: 3286, Value: 200},
|
||||
{ID: 3287, Value: 200},
|
||||
{ID: 3288, Value: 200},
|
||||
{ID: 3289, Value: 200},
|
||||
{ID: 3290, Value: 200},
|
||||
{ID: 3291, Value: 200},
|
||||
{ID: 3292, Value: 200},
|
||||
{ID: 3293, Value: 200},
|
||||
{ID: 3294, Value: 200},
|
||||
{ID: 3295, Value: 200},
|
||||
{ID: 3296, Value: 200},
|
||||
{ID: 3297, Value: 200},
|
||||
{ID: 3298, Value: 200},
|
||||
{ID: 3299, Value: 200},
|
||||
{ID: 3300, Value: 200},
|
||||
{ID: 3301, Value: 200},
|
||||
{ID: 3302, Value: 200},
|
||||
{ID: 3303, Value: 200},
|
||||
{ID: 3304, Value: 200},
|
||||
{ID: 3305, Value: 200},
|
||||
{ID: 3306, Value: 200},
|
||||
{ID: 3307, Value: 200},
|
||||
{ID: 3308, Value: 200},
|
||||
{ID: 3309, Value: 200},
|
||||
{ID: 3310, Value: 200},
|
||||
{ID: 3311, Value: 200},
|
||||
{ID: 3312, Value: 300},
|
||||
{ID: 3313, Value: 300},
|
||||
{ID: 3314, Value: 300},
|
||||
{ID: 3315, Value: 300},
|
||||
{ID: 3316, Value: 300},
|
||||
{ID: 3317, Value: 300},
|
||||
{ID: 3318, Value: 300},
|
||||
{ID: 3319, Value: 300},
|
||||
{ID: 3320, Value: 300},
|
||||
{ID: 3321, Value: 300},
|
||||
{ID: 3322, Value: 300},
|
||||
{ID: 3323, Value: 300},
|
||||
{ID: 3324, Value: 300},
|
||||
{ID: 3325, Value: 300},
|
||||
{ID: 3326, Value: 300},
|
||||
{ID: 3327, Value: 300},
|
||||
{ID: 3328, Value: 300},
|
||||
{ID: 3329, Value: 300},
|
||||
{ID: 3330, Value: 300},
|
||||
{ID: 3331, Value: 300},
|
||||
{ID: 3332, Value: 300},
|
||||
{ID: 3333, Value: 300},
|
||||
{ID: 3334, Value: 300},
|
||||
{ID: 3335, Value: 300},
|
||||
{ID: 3336, Value: 300},
|
||||
{ID: 3337, Value: 300},
|
||||
{ID: 3338, Value: 100},
|
||||
{ID: 3339, Value: 100},
|
||||
{ID: 3340, Value: 100},
|
||||
{ID: 3341, Value: 100},
|
||||
{ID: 3342, Value: 100},
|
||||
{ID: 3343, Value: 100},
|
||||
{ID: 3344, Value: 100},
|
||||
{ID: 3345, Value: 100},
|
||||
{ID: 3346, Value: 100},
|
||||
{ID: 3347, Value: 100},
|
||||
{ID: 3348, Value: 100},
|
||||
{ID: 3349, Value: 100},
|
||||
{ID: 3350, Value: 100},
|
||||
{ID: 3351, Value: 100},
|
||||
{ID: 3352, Value: 100},
|
||||
{ID: 3353, Value: 100},
|
||||
{ID: 3354, Value: 100},
|
||||
{ID: 3355, Value: 100},
|
||||
{ID: 3356, Value: 100},
|
||||
{ID: 3357, Value: 100},
|
||||
{ID: 3358, Value: 100},
|
||||
{ID: 3359, Value: 100},
|
||||
{ID: 3360, Value: 100},
|
||||
{ID: 3361, Value: 100},
|
||||
{ID: 3362, Value: 100},
|
||||
{ID: 3363, Value: 100},
|
||||
{ID: 3364, Value: 100},
|
||||
{ID: 3365, Value: 100},
|
||||
{ID: 3366, Value: 100},
|
||||
{ID: 3367, Value: 100},
|
||||
{ID: 3368, Value: 100},
|
||||
{ID: 3369, Value: 100},
|
||||
{ID: 3370, Value: 100},
|
||||
{ID: 3371, Value: 100},
|
||||
{ID: 3372, Value: 100},
|
||||
{ID: 3373, Value: 100},
|
||||
{ID: 3374, Value: 100},
|
||||
{ID: 3375, Value: 100},
|
||||
{ID: 3376, Value: 100},
|
||||
{ID: 3377, Value: 100},
|
||||
{ID: 3378, Value: 100},
|
||||
{ID: 3379, Value: 100},
|
||||
{ID: 3380, Value: 100},
|
||||
{ID: 3381, Value: 100},
|
||||
{ID: 3382, Value: 100},
|
||||
{ID: 3383, Value: 100},
|
||||
{ID: 3384, Value: 100},
|
||||
{ID: 3385, Value: 100},
|
||||
{ID: 3386, Value: 100},
|
||||
{ID: 3387, Value: 100},
|
||||
{ID: 3388, Value: 100},
|
||||
{ID: 3389, Value: 100},
|
||||
{ID: 3390, Value: 100},
|
||||
{ID: 3391, Value: 100},
|
||||
{ID: 3392, Value: 100},
|
||||
{ID: 3393, Value: 100},
|
||||
{ID: 3394, Value: 100},
|
||||
{ID: 3395, Value: 100},
|
||||
{ID: 3396, Value: 100},
|
||||
{ID: 3397, Value: 100},
|
||||
{ID: 3398, Value: 100},
|
||||
{ID: 3399, Value: 100},
|
||||
{ID: 3400, Value: 100},
|
||||
{ID: 3401, Value: 100},
|
||||
{ID: 3402, Value: 100},
|
||||
{ID: 3416, Value: 100},
|
||||
{ID: 3417, Value: 100},
|
||||
{ID: 3418, Value: 100},
|
||||
{ID: 3419, Value: 100},
|
||||
{ID: 3420, Value: 100},
|
||||
{ID: 3421, Value: 100},
|
||||
{ID: 3422, Value: 100},
|
||||
{ID: 3423, Value: 100},
|
||||
{ID: 3424, Value: 100},
|
||||
{ID: 3425, Value: 100},
|
||||
{ID: 3426, Value: 100},
|
||||
{ID: 3427, Value: 100},
|
||||
{ID: 3428, Value: 100},
|
||||
{ID: 3442, Value: 100},
|
||||
{ID: 3443, Value: 100},
|
||||
{ID: 3444, Value: 100},
|
||||
{ID: 3445, Value: 100},
|
||||
{ID: 3446, Value: 100},
|
||||
{ID: 3447, Value: 100},
|
||||
{ID: 3448, Value: 100},
|
||||
{ID: 3449, Value: 100},
|
||||
{ID: 3450, Value: 100},
|
||||
{ID: 3451, Value: 100},
|
||||
{ID: 3452, Value: 100},
|
||||
{ID: 3453, Value: 100},
|
||||
{ID: 3454, Value: 100},
|
||||
{ID: 3468, Value: 100},
|
||||
{ID: 3469, Value: 100},
|
||||
{ID: 3470, Value: 100},
|
||||
{ID: 3471, Value: 100},
|
||||
{ID: 3472, Value: 100},
|
||||
{ID: 3473, Value: 100},
|
||||
{ID: 3474, Value: 100},
|
||||
{ID: 3475, Value: 100},
|
||||
{ID: 3476, Value: 100},
|
||||
{ID: 3477, Value: 100},
|
||||
{ID: 3478, Value: 100},
|
||||
{ID: 3479, Value: 100},
|
||||
{ID: 3480, Value: 100},
|
||||
{ID: 3494, Value: 0},
|
||||
{ID: 3495, Value: 0},
|
||||
{ID: 3496, Value: 0},
|
||||
{ID: 3497, Value: 0},
|
||||
{ID: 3498, Value: 0},
|
||||
{ID: 3499, Value: 0},
|
||||
{ID: 3500, Value: 0},
|
||||
{ID: 3501, Value: 0},
|
||||
{ID: 3502, Value: 0},
|
||||
{ID: 3503, Value: 0},
|
||||
{ID: 3504, Value: 0},
|
||||
{ID: 3505, Value: 0},
|
||||
{ID: 3506, Value: 0},
|
||||
{ID: 3520, Value: 0},
|
||||
{ID: 3521, Value: 0},
|
||||
{ID: 3522, Value: 0},
|
||||
{ID: 3523, Value: 0},
|
||||
{ID: 3524, Value: 0},
|
||||
{ID: 3525, Value: 0},
|
||||
{ID: 3526, Value: 0},
|
||||
{ID: 3527, Value: 0},
|
||||
{ID: 3528, Value: 0},
|
||||
{ID: 3529, Value: 0},
|
||||
{ID: 3530, Value: 0},
|
||||
{ID: 3531, Value: 0},
|
||||
{ID: 3532, Value: 0},
|
||||
{ID: 3546, Value: 0},
|
||||
{ID: 3547, Value: 0},
|
||||
{ID: 3548, Value: 0},
|
||||
{ID: 3549, Value: 0},
|
||||
{ID: 3550, Value: 0},
|
||||
{ID: 3551, Value: 0},
|
||||
{ID: 3552, Value: 0},
|
||||
{ID: 3553, Value: 0},
|
||||
{ID: 3554, Value: 0},
|
||||
{ID: 3555, Value: 0},
|
||||
{ID: 3556, Value: 0},
|
||||
{ID: 3557, Value: 0},
|
||||
{ID: 3558, Value: 0},
|
||||
{ID: 3572, Value: 0},
|
||||
{ID: 3573, Value: 0},
|
||||
{ID: 3574, Value: 0},
|
||||
{ID: 3575, Value: 0},
|
||||
{ID: 3576, Value: 0},
|
||||
{ID: 3577, Value: 0},
|
||||
{ID: 3578, Value: 0},
|
||||
{ID: 3579, Value: 0},
|
||||
{ID: 3580, Value: 0},
|
||||
{ID: 3581, Value: 0},
|
||||
{ID: 3582, Value: 0},
|
||||
{ID: 3583, Value: 0},
|
||||
{ID: 3584, Value: 0},
|
||||
}
|
||||
|
||||
tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)})
|
||||
@@ -653,65 +512,70 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.EnableKaijiEvent {
|
||||
tuneValues = append(tuneValues, tuneValue{1106, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1106, 0})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent {
|
||||
tuneValues = append(tuneValues, tuneValue{1144, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1144, 0})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.EnableNierEvent {
|
||||
tuneValues = append(tuneValues, tuneValue{1153, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1153, 0})
|
||||
}
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.DisableRoad {
|
||||
tuneValues = append(tuneValues, tuneValue{1155, 1})
|
||||
} else {
|
||||
tuneValues = append(tuneValues, tuneValue{1155, 0})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier * 100)})
|
||||
}
|
||||
// get_hrp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3000, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3338, uint16(s.server.erupeConfig.GameplayOptions.HRPMultiplierNC*100))...)
|
||||
// get_srp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3013, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3351, uint16(s.server.erupeConfig.GameplayOptions.SRPMultiplierNC*100))...)
|
||||
// get_grp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3364, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplierNC*100))...)
|
||||
// get_gsrp_rate_from_rank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3377, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplierNC*100))...)
|
||||
// get_zeny_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3052, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3390, uint16(s.server.erupeConfig.GameplayOptions.ZennyMultiplierNC*100))...)
|
||||
// get_zeny_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3416, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplierNC*100))...)
|
||||
// get_reward_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3442, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplierNC*100))...)
|
||||
// get_reward_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3130, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplier*100))...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3468, uint16(s.server.erupeConfig.GameplayOptions.GMaterialMultiplierNC*100))...)
|
||||
// get_lottery_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3156, 0)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3494, 0)...)
|
||||
// get_lottery_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3182, 0)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3520, 0)...)
|
||||
// get_hagi_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3208, s.server.erupeConfig.GameplayOptions.ExtraCarves)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3546, s.server.erupeConfig.GameplayOptions.ExtraCarvesNC)...)
|
||||
// get_hagi_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3234, s.server.erupeConfig.GameplayOptions.GExtraCarves)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3572, s.server.erupeConfig.GameplayOptions.GExtraCarvesNC)...)
|
||||
// get_nboost_transcend_rate_from_hrank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3286, 200)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3312, 300)...)
|
||||
// get_nboost_transcend_rate_from_grank
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...)
|
||||
tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...)
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)})
|
||||
var temp []tuneValue
|
||||
for i := range tuneValues {
|
||||
if tuneValues[i].Value > 0 {
|
||||
temp = append(temp, tuneValues[i])
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3052, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3130, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
|
||||
}
|
||||
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3156, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3182, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3208, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tuneValues = append(tuneValues, tuneValue{i + 3234, s.server.erupeConfig.GameplayOptions.ExtraCarves})
|
||||
}
|
||||
|
||||
offset := uint16(time.Now().Unix())
|
||||
bf.WriteUint16(offset)
|
||||
tuneValues = temp
|
||||
|
||||
tuneLimit := 770
|
||||
if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
@@ -737,6 +601,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
tuneValues = tuneValues[:tuneLimit]
|
||||
}
|
||||
|
||||
offset := uint16(time.Now().Unix())
|
||||
bf.WriteUint16(offset)
|
||||
|
||||
bf.WriteUint16(uint16(len(tuneValues)))
|
||||
for i := range tuneValues {
|
||||
bf.WriteUint16(tuneValues[i].ID ^ offset)
|
||||
@@ -778,6 +645,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func getTuneValueRange(start uint16, value uint16) []tuneValue {
|
||||
var tv []tuneValue
|
||||
for i := uint16(0); i < 13; i++ {
|
||||
tv = append(tv, tuneValue{start + i, value})
|
||||
}
|
||||
return tv
|
||||
}
|
||||
|
||||
func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -157,7 +157,6 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.stage.reservedClientSlots[s.charID] = false
|
||||
s.stage.Unlock()
|
||||
s.stageMoveStack.Push(s.stage.id)
|
||||
s.stageMoveStack.Lock()
|
||||
}
|
||||
|
||||
if s.reservationStage != nil {
|
||||
@@ -171,7 +170,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysBackStage)
|
||||
|
||||
// Transfer back to the saved stage ID before the previous move or enter.
|
||||
s.stageMoveStack.Unlock()
|
||||
backStage, err := s.stageMoveStack.Pop()
|
||||
if backStage == "" || err != nil {
|
||||
backStage = "sl1Ns200p0a0u0"
|
||||
@@ -190,12 +188,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysMoveStage)
|
||||
|
||||
// Set a new move stack from the given stage ID
|
||||
if !s.stageMoveStack.Locked {
|
||||
s.stageMoveStack.Set(pkt.StageID)
|
||||
}
|
||||
|
||||
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
@@ -16,8 +18,8 @@ type TowerInfoTRP struct {
|
||||
}
|
||||
|
||||
type TowerInfoSkill struct {
|
||||
TSP int32
|
||||
Unk1 []int16 // 40
|
||||
TSP int32
|
||||
Skills []int16 // 64
|
||||
}
|
||||
|
||||
type TowerInfoHistory struct {
|
||||
@@ -32,6 +34,14 @@ type TowerInfoLevel struct {
|
||||
Unk3 int32
|
||||
}
|
||||
|
||||
func EmptyTowerCSV(len int) string {
|
||||
temp := make([]string, len)
|
||||
for i := range temp {
|
||||
temp[i] = "0"
|
||||
}
|
||||
return strings.Join(temp, ",")
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
|
||||
var data []*byteframe.ByteFrame
|
||||
@@ -44,21 +54,24 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
towerInfo := TowerInfo{
|
||||
TRP: []TowerInfoTRP{{0, 0}},
|
||||
Skill: []TowerInfoSkill{{0, make([]int16, 40)}},
|
||||
Skill: []TowerInfoSkill{{0, make([]int16, 64)}},
|
||||
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
|
||||
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}},
|
||||
}
|
||||
|
||||
tempSkills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), skills FROM tower WHERE char_id=$1
|
||||
`, s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
|
||||
var tempSkills string
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), COALESCE(skills, $1) FROM tower WHERE char_id=$2
|
||||
`, EmptyTowerCSV(64), s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
|
||||
if err != nil {
|
||||
s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID)
|
||||
}
|
||||
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
towerInfo.Level = towerInfo.Level[:1]
|
||||
}
|
||||
|
||||
for i, skill := range stringsupport.CSVElems(tempSkills) {
|
||||
towerInfo.Skill[0].Unk1[i] = int16(skill)
|
||||
towerInfo.Skill[0].Skills[i] = int16(skill)
|
||||
}
|
||||
|
||||
switch pkt.InfoType {
|
||||
@@ -73,8 +86,8 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
for _, skills := range towerInfo.Skill {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(skills.TSP)
|
||||
for i := range skills.Unk1 {
|
||||
bf.WriteInt16(skills.Unk1[i])
|
||||
for i := range skills.Skills {
|
||||
bf.WriteInt16(skills.Skills[i])
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
@@ -89,7 +102,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
case 3, 5:
|
||||
for _, level := range towerInfo.Level {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(level.Floors)
|
||||
@@ -105,7 +118,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("InfoType", pkt.InfoType),
|
||||
@@ -123,8 +136,8 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
switch pkt.InfoType {
|
||||
case 2:
|
||||
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
|
||||
var skills string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(skills, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(64), s.charID).Scan(&skills)
|
||||
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
|
||||
case 1, 7:
|
||||
// This might give too much TSP? No idea what the rate is supposed to be
|
||||
@@ -328,7 +341,7 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint8("Unk0", pkt.Unk0),
|
||||
@@ -412,10 +425,10 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
gemInfo := []GemInfo{}
|
||||
gemHistory := []GemHistory{}
|
||||
|
||||
tempGems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems)
|
||||
var tempGems string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&tempGems)
|
||||
for i, v := range stringsupport.CSVElems(tempGems) {
|
||||
gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)})
|
||||
gemInfo = append(gemInfo, GemInfo{uint16((i / 5 << 8) + (i%5 + 1)), uint16(v)})
|
||||
}
|
||||
|
||||
switch pkt.Unk0 {
|
||||
@@ -442,7 +455,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
if s.server.erupeConfig.DebugOptions.QuestTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("Op", pkt.Op),
|
||||
@@ -455,11 +468,11 @@ func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
)
|
||||
}
|
||||
|
||||
gems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems)
|
||||
var gems string
|
||||
s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&gems)
|
||||
switch pkt.Op {
|
||||
case 1: // Add gem
|
||||
i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5))
|
||||
i := int((pkt.Gem >> 8 * 5) + (pkt.Gem - pkt.Gem&0xFF00 - 1%5))
|
||||
s.server.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, stringsupport.CSVSetIndex(gems, i, stringsupport.CSVGetIndex(gems, i)+int(pkt.Quantity)), s.charID)
|
||||
case 2: // Transfer gem
|
||||
// no way im doing this for now
|
||||
|
||||
@@ -57,7 +57,7 @@ type Server struct {
|
||||
stages map[string]*Stage
|
||||
|
||||
// Used to map different languages
|
||||
dict map[string]string
|
||||
i18n i18n
|
||||
|
||||
// UserBinary
|
||||
userBinaryPartsLock sync.RWMutex
|
||||
@@ -192,7 +192,7 @@ func NewServer(config *Config) *Server {
|
||||
// MezFes
|
||||
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
|
||||
|
||||
s.dict = getLangStrings(s)
|
||||
s.i18n = getLangStrings(s)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -211,6 +211,7 @@ func (s *Server) Start() error {
|
||||
// Start the discord bot for chat integration.
|
||||
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
|
||||
s.discordBot.Session.AddHandler(s.onDiscordMessage)
|
||||
s.discordBot.Session.AddHandler(s.onInteraction)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -336,13 +337,13 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
|
||||
var text string
|
||||
switch _type {
|
||||
case 2:
|
||||
text = s.dict["ravienteBerserk"]
|
||||
text = s.i18n.raviente.berserk
|
||||
case 3:
|
||||
text = s.dict["ravienteExtreme"]
|
||||
text = s.i18n.raviente.extreme
|
||||
case 4:
|
||||
text = s.dict["ravienteExtremeLimited"]
|
||||
text = s.i18n.raviente.extremeLimited
|
||||
case 5:
|
||||
text = s.dict["ravienteBerserkSmall"]
|
||||
text = s.i18n.raviente.berserkSmall
|
||||
default:
|
||||
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
|
||||
}
|
||||
@@ -377,6 +378,26 @@ func (s *Server) FindSessionByCharID(charID uint32) *Session {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) DisconnectUser(uid uint32) {
|
||||
var cid uint32
|
||||
var cids []uint32
|
||||
rows, _ := s.db.Query(`SELECT id FROM characters WHERE user_id=$1`, uid)
|
||||
for rows.Next() {
|
||||
rows.Scan(&cid)
|
||||
cids = append(cids, cid)
|
||||
}
|
||||
for _, c := range s.Channels {
|
||||
for _, session := range c.sessions {
|
||||
for _, cid := range cids {
|
||||
if session.charID == cid {
|
||||
session.rawConn.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) FindObjectByChar(charID uint32) *Object {
|
||||
s.stagesLock.RLock()
|
||||
defer s.stagesLock.RUnlock()
|
||||
|
||||
@@ -1,114 +1,238 @@
|
||||
package channelserver
|
||||
|
||||
func getLangStrings(s *Server) map[string]string {
|
||||
strings := make(map[string]string)
|
||||
type i18n struct {
|
||||
language string
|
||||
cafe struct {
|
||||
reset string
|
||||
}
|
||||
timer string
|
||||
commands struct {
|
||||
noOp string
|
||||
disabled string
|
||||
reload string
|
||||
kqf struct {
|
||||
get string
|
||||
set struct {
|
||||
error string
|
||||
success string
|
||||
}
|
||||
version string
|
||||
}
|
||||
rights struct {
|
||||
error string
|
||||
success string
|
||||
}
|
||||
course struct {
|
||||
error string
|
||||
disabled string
|
||||
enabled string
|
||||
locked string
|
||||
}
|
||||
teleport struct {
|
||||
error string
|
||||
success string
|
||||
}
|
||||
psn struct {
|
||||
error string
|
||||
success string
|
||||
exists string
|
||||
}
|
||||
discord struct {
|
||||
success string
|
||||
}
|
||||
ban struct {
|
||||
success string
|
||||
noUser string
|
||||
invalid string
|
||||
error string
|
||||
length string
|
||||
}
|
||||
timer struct {
|
||||
enabled string
|
||||
disabled string
|
||||
}
|
||||
ravi struct {
|
||||
noCommand string
|
||||
start struct {
|
||||
success string
|
||||
error string
|
||||
}
|
||||
multiplier string
|
||||
res struct {
|
||||
success string
|
||||
error string
|
||||
}
|
||||
sed struct {
|
||||
success string
|
||||
}
|
||||
request string
|
||||
error string
|
||||
noPlayers string
|
||||
version string
|
||||
}
|
||||
}
|
||||
raviente struct {
|
||||
berserk string
|
||||
extreme string
|
||||
extremeLimited string
|
||||
berserkSmall string
|
||||
}
|
||||
guild struct {
|
||||
invite struct {
|
||||
title string
|
||||
body string
|
||||
success struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
accepted struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
rejected struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
declined struct {
|
||||
title string
|
||||
body string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getLangStrings(s *Server) i18n {
|
||||
var i i18n
|
||||
switch s.erupeConfig.Language {
|
||||
case "jp":
|
||||
strings["language"] = "日本語"
|
||||
strings["cafeReset"] = "%d/%dにリセット"
|
||||
i.language = "日本語"
|
||||
i.cafe.reset = "%d/%dにリセット"
|
||||
i.timer = "タイマー:%02d'%02d\"%02d.%03d (%df)"
|
||||
|
||||
strings["commandDisabled"] = "%sのコマンドは無効です"
|
||||
strings["commandReload"] = "リロードします"
|
||||
strings["commandKqfGet"] = "現在のキークエストフラグ:%x"
|
||||
strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
|
||||
strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
|
||||
strings["commandKqfVersion"] = "This command is disabled prior to MHFG10"
|
||||
strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x"
|
||||
strings["commandRightsSuccess"] = "コース情報を更新しました:%d"
|
||||
strings["commandCourseError"] = "コース確認コマンドエラー 例:%s <name>"
|
||||
strings["commandCourseDisabled"] = "%sコースは無効です"
|
||||
strings["commandCourseEnabled"] = "%sコースは有効です"
|
||||
strings["commandCourseLocked"] = "%sコースはロックされています"
|
||||
strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y"
|
||||
strings["commandTeleportSuccess"] = "%d %dにテレポート"
|
||||
strings["commandPSNError"] = "PSN連携コマンドエラー 例:%s <psn id>"
|
||||
strings["commandPSNSuccess"] = "PSN「%s」が連携されています"
|
||||
strings["commandPSNExists"] = "PSNは既存のユーザに接続されています"
|
||||
i.commands.noOp = "You don't have permission to use this command"
|
||||
i.commands.disabled = "%sのコマンドは無効です"
|
||||
i.commands.reload = "リロードします"
|
||||
i.commands.kqf.get = "現在のキークエストフラグ:%x"
|
||||
i.commands.kqf.set.error = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
|
||||
i.commands.kqf.set.success = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
|
||||
i.commands.kqf.version = "This command is disabled prior to MHFG10"
|
||||
i.commands.rights.error = "コース更新コマンドエラー 例:%s x"
|
||||
i.commands.rights.success = "コース情報を更新しました:%d"
|
||||
i.commands.course.error = "コース確認コマンドエラー 例:%s <name>"
|
||||
i.commands.course.disabled = "%sコースは無効です"
|
||||
i.commands.course.enabled = "%sコースは有効です"
|
||||
i.commands.course.locked = "%sコースはロックされています"
|
||||
i.commands.teleport.error = "テレポートコマンドエラー 構文:%s x y"
|
||||
i.commands.teleport.success = "%d %dにテレポート"
|
||||
i.commands.psn.error = "PSN連携コマンドエラー 例:%s <psn id>"
|
||||
i.commands.psn.success = "PSN「%s」が連携されています"
|
||||
i.commands.psn.exists = "PSNは既存のユーザに接続されています"
|
||||
|
||||
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
|
||||
strings["commandRaviStartSuccess"] = "大討伐を開始します"
|
||||
strings["commandRaviStartError"] = "大討伐は既に開催されています"
|
||||
strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%.2f"
|
||||
strings["commandRaviResSuccess"] = "復活支援を実行します"
|
||||
strings["commandRaviResError"] = "復活支援は実行されませんでした"
|
||||
strings["commandRaviSedSuccess"] = "鎮静支援を実行します"
|
||||
strings["commandRaviRequest"] = "鎮静支援を要請します"
|
||||
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
|
||||
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
|
||||
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
|
||||
i.commands.discord.success = "あなたのDiscordトークン:%s"
|
||||
|
||||
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
|
||||
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
|
||||
i.commands.ban.noUser = "Could not find user"
|
||||
i.commands.ban.success = "Successfully banned %s"
|
||||
i.commands.ban.invalid = "Invalid Character ID"
|
||||
i.commands.ban.error = "Error in command. Format: %s <id> [length]"
|
||||
i.commands.ban.length = " until %s"
|
||||
|
||||
strings["guildInviteName"] = "猟団勧誘のご案内"
|
||||
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
|
||||
i.commands.ravi.noCommand = "ラヴィコマンドが指定されていません"
|
||||
i.commands.ravi.start.success = "大討伐を開始します"
|
||||
i.commands.ravi.start.error = "大討伐は既に開催されています"
|
||||
i.commands.ravi.multiplier = "ラヴィダメージ倍率:x%.2f"
|
||||
i.commands.ravi.res.success = "復活支援を実行します"
|
||||
i.commands.ravi.res.error = "復活支援は実行されませんでした"
|
||||
i.commands.ravi.sed.success = "鎮静支援を実行します"
|
||||
i.commands.ravi.request = "鎮静支援を要請します"
|
||||
i.commands.ravi.error = "ラヴィコマンドが認識されません"
|
||||
i.commands.ravi.noPlayers = "誰も大討伐に参加していません"
|
||||
i.commands.ravi.version = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["guildInviteSuccessName"] = "成功"
|
||||
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
|
||||
i.raviente.berserk = "<大討伐:猛狂期>が開催されました!"
|
||||
i.raviente.extreme = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
i.raviente.extremeLimited = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
|
||||
i.raviente.berserkSmall = "<大討伐:猛狂期(小数)>が開催されました!"
|
||||
|
||||
strings["guildInviteAcceptedName"] = "承諾されました"
|
||||
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
|
||||
i.guild.invite.title = "猟団勧誘のご案内"
|
||||
i.guild.invite.body = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
|
||||
|
||||
strings["guildInviteRejectName"] = "却下しました"
|
||||
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
|
||||
i.guild.invite.success.title = "成功"
|
||||
i.guild.invite.success.body = "あなたは「%s」に参加できました。"
|
||||
|
||||
strings["guildInviteDeclinedName"] = "辞退しました"
|
||||
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
|
||||
i.guild.invite.accepted.title = "承諾されました"
|
||||
i.guild.invite.accepted.body = "招待した狩人が「%s」への招待を承諾しました。"
|
||||
|
||||
i.guild.invite.rejected.title = "却下しました"
|
||||
i.guild.invite.rejected.body = "あなたは「%s」への参加を却下しました。"
|
||||
|
||||
i.guild.invite.declined.title = "辞退しました"
|
||||
i.guild.invite.declined.body = "招待した狩人が「%s」への招待を辞退しました。"
|
||||
default:
|
||||
strings["language"] = "English"
|
||||
strings["cafeReset"] = "Resets on %d/%d"
|
||||
i.language = "English"
|
||||
i.cafe.reset = "Resets on %d/%d"
|
||||
i.timer = "Time: %02d:%02d:%02d.%03d (%df)"
|
||||
|
||||
strings["commandDisabled"] = "%s command is disabled"
|
||||
strings["commandReload"] = "Reloading players..."
|
||||
strings["commandKqfGet"] = "KQF: %x"
|
||||
strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
|
||||
strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World"
|
||||
strings["commandKqfVersion"] = "This command is disabled prior to MHFG10"
|
||||
strings["commandRightsError"] = "Error in command. Format: %s x"
|
||||
strings["commandRightsSuccess"] = "Set rights integer: %d"
|
||||
strings["commandCourseError"] = "Error in command. Format: %s <name>"
|
||||
strings["commandCourseDisabled"] = "%s Course disabled"
|
||||
strings["commandCourseEnabled"] = "%s Course enabled"
|
||||
strings["commandCourseLocked"] = "%s Course is locked"
|
||||
strings["commandTeleportError"] = "Error in command. Format: %s x y"
|
||||
strings["commandTeleportSuccess"] = "Teleporting to %d %d"
|
||||
strings["commandPSNError"] = "Error in command. Format: %s <psn id>"
|
||||
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
|
||||
strings["commandPSNExists"] = "PSN ID is connected to another account!"
|
||||
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.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"
|
||||
i.commands.kqf.version = "This command is disabled prior to MHFG10"
|
||||
i.commands.rights.error = "Error in command. Format: %s x"
|
||||
i.commands.rights.success = "Set rights integer: %d"
|
||||
i.commands.course.error = "Error in command. Format: %s <name>"
|
||||
i.commands.course.disabled = "%s Course disabled"
|
||||
i.commands.course.enabled = "%s Course enabled"
|
||||
i.commands.course.locked = "%s Course is locked"
|
||||
i.commands.teleport.error = "Error in command. Format: %s x y"
|
||||
i.commands.teleport.success = "Teleporting to %d %d"
|
||||
i.commands.psn.error = "Error in command. Format: %s <psn id>"
|
||||
i.commands.psn.success = "Connected PSN ID: %s"
|
||||
i.commands.psn.exists = "PSN ID is connected to another account!"
|
||||
|
||||
strings["commandRaviNoCommand"] = "No Raviente command specified!"
|
||||
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
|
||||
strings["commandRaviStartError"] = "The Great Slaying has already begun!"
|
||||
strings["commandRaviMultiplier"] = "Raviente multiplier is currently %.2fx"
|
||||
strings["commandRaviResSuccess"] = "Sending resurrection support!"
|
||||
strings["commandRaviResError"] = "Resurrection support has not been requested!"
|
||||
strings["commandRaviSedSuccess"] = "Sending sedation support if requested!"
|
||||
strings["commandRaviRequest"] = "Requesting sedation support!"
|
||||
strings["commandRaviError"] = "Raviente command not recognised!"
|
||||
strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!"
|
||||
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
|
||||
i.commands.discord.success = "Your Discord token: %s"
|
||||
|
||||
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
|
||||
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
|
||||
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
|
||||
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!"
|
||||
i.commands.ban.noUser = "Could not find user"
|
||||
i.commands.ban.success = "Successfully banned %s"
|
||||
i.commands.ban.invalid = "Invalid Character ID"
|
||||
i.commands.ban.error = "Error in command. Format: %s <id> [length]"
|
||||
i.commands.ban.length = " until %s"
|
||||
|
||||
strings["guildInviteName"] = "Invitation!"
|
||||
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
|
||||
i.commands.timer.enabled = "Quest timer enabled"
|
||||
i.commands.timer.disabled = "Quest timer disabled"
|
||||
|
||||
strings["guildInviteSuccessName"] = "Success!"
|
||||
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
|
||||
i.commands.ravi.noCommand = "No Raviente command specified!"
|
||||
i.commands.ravi.start.success = "The Great Slaying will begin in a moment"
|
||||
i.commands.ravi.start.error = "The Great Slaying has already begun!"
|
||||
i.commands.ravi.multiplier = "Raviente multiplier is currently %.2fx"
|
||||
i.commands.ravi.res.success = "Sending resurrection support!"
|
||||
i.commands.ravi.res.error = "Resurrection support has not been requested!"
|
||||
i.commands.ravi.sed.success = "Sending sedation support if requested!"
|
||||
i.commands.ravi.request = "Requesting sedation support!"
|
||||
i.commands.ravi.error = "Raviente command not recognised!"
|
||||
i.commands.ravi.noPlayers = "No one has joined the Great Slaying!"
|
||||
i.commands.ravi.version = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["guildInviteAcceptedName"] = "Accepted"
|
||||
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
|
||||
i.raviente.berserk = "<Great Slaying: Berserk> is being held!"
|
||||
i.raviente.extreme = "<Great Slaying: Extreme> is being held!"
|
||||
i.raviente.extremeLimited = "<Great Slaying: Extreme (Limited)> is being held!"
|
||||
i.raviente.berserkSmall = "<Great Slaying: Berserk (Small)> is being held!"
|
||||
|
||||
strings["guildInviteRejectName"] = "Rejected"
|
||||
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
|
||||
i.guild.invite.title = "Invitation!"
|
||||
i.guild.invite.body = "You have been invited to join\n「%s」\nDo you want to accept?"
|
||||
|
||||
strings["guildInviteDeclinedName"] = "Declined"
|
||||
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
|
||||
i.guild.invite.success.title = "Success!"
|
||||
i.guild.invite.success.body = "You have successfully joined\n「%s」."
|
||||
|
||||
i.guild.invite.accepted.title = "Accepted"
|
||||
i.guild.invite.accepted.body = "The recipient accepted your invitation to join\n「%s」."
|
||||
|
||||
i.guild.invite.rejected.title = "Rejected"
|
||||
i.guild.invite.rejected.body = "You rejected the invitation to join\n「%s」."
|
||||
|
||||
i.guild.invite.declined.title = "Declined"
|
||||
i.guild.invite.declined.body = "The recipient declined your invitation to join\n「%s」."
|
||||
}
|
||||
return strings
|
||||
return i
|
||||
}
|
||||
|
||||
@@ -86,13 +86,11 @@ func NewSession(server *Server, conn net.Conn) *Session {
|
||||
|
||||
// Start starts the session packet send and recv loop(s).
|
||||
func (s *Session) Start() {
|
||||
go func() {
|
||||
s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String()))
|
||||
// Unlike the sign and entrance server,
|
||||
// the client DOES NOT initalize the channel connection with 8 NULL bytes.
|
||||
go s.sendLoop()
|
||||
s.recvLoop()
|
||||
}()
|
||||
s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String()))
|
||||
// Unlike the sign and entrance server,
|
||||
// the client DOES NOT initalize the channel connection with 8 NULL bytes.
|
||||
go s.sendLoop()
|
||||
go s.recvLoop()
|
||||
}
|
||||
|
||||
// QueueSend queues a packet (raw []byte) to be sent.
|
||||
@@ -150,16 +148,19 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) {
|
||||
}
|
||||
|
||||
func (s *Session) sendLoop() {
|
||||
var pkt packet
|
||||
for {
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
pkt := <-s.sendPackets
|
||||
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to send packet")
|
||||
for len(s.sendPackets) > 0 {
|
||||
pkt = <-s.sendPackets
|
||||
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to send packet")
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,14 +179,13 @@ func (s *Session) recvLoop() {
|
||||
s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
s.handlePacketGroup(pkt)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,13 +253,9 @@ func ignored(opcode network.PacketID) bool {
|
||||
}
|
||||
|
||||
func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) {
|
||||
if !s.server.erupeConfig.DevMode {
|
||||
if sender == "Server" && !s.server.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
return
|
||||
}
|
||||
|
||||
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
return
|
||||
} else if sender != "Server" && !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
} else if sender != "Server" && !s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -277,8 +273,8 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
|
||||
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
|
||||
}
|
||||
fmt.Printf("Opcode: %s\n", opcodePID)
|
||||
if s.server.erupeConfig.DevModeOptions.LogMessageData {
|
||||
if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength {
|
||||
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))
|
||||
} else {
|
||||
fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data))
|
||||
@@ -313,3 +309,12 @@ func (s *Session) NextObjectID() uint32 {
|
||||
bf.Seek(0, 0)
|
||||
return bf.ReadUint32()
|
||||
}
|
||||
|
||||
func (s *Session) isOp() 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)
|
||||
if err == nil && op {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,12 +7,39 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var Commands = []*discordgo.ApplicationCommand{
|
||||
{
|
||||
Name: "link",
|
||||
Description: "Link your Erupe account to Discord",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "token",
|
||||
Description: "The token provided by the Discord command in-game",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Description: "Change your Erupe account password",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "password",
|
||||
Description: "Your new password",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type DiscordBot struct {
|
||||
Session *discordgo.Session
|
||||
config *_config.Config
|
||||
logger *zap.Logger
|
||||
MainGuild *discordgo.Guild
|
||||
RealtimeChannel *discordgo.Channel
|
||||
Session *discordgo.Session
|
||||
config *_config.Config
|
||||
logger *zap.Logger
|
||||
MainGuild *discordgo.Guild
|
||||
RelayChannel *discordgo.Channel
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -28,18 +55,22 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID)
|
||||
var relayChannel *discordgo.Channel
|
||||
|
||||
if options.Config.Discord.RelayChannel.Enabled {
|
||||
relayChannel, err = session.Channel(options.Config.Discord.RelayChannel.RelayChannelID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
options.Logger.Fatal("Discord failed to create realtimeChannel", zap.Error(err))
|
||||
options.Logger.Fatal("Discord failed to create relayChannel", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discordBot = &DiscordBot{
|
||||
config: options.Config,
|
||||
logger: options.Logger,
|
||||
Session: session,
|
||||
RealtimeChannel: realtimeChannel,
|
||||
config: options.Config,
|
||||
logger: options.Logger,
|
||||
Session: session,
|
||||
RelayChannel: relayChannel,
|
||||
}
|
||||
|
||||
return
|
||||
@@ -51,7 +82,7 @@ func (bot *DiscordBot) Start() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Replace all mentions to real name from the message.
|
||||
// NormalizeDiscordMessage replaces all mentions to real name from the message.
|
||||
func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
|
||||
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
|
||||
emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`)
|
||||
@@ -74,7 +105,11 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
|
||||
}
|
||||
|
||||
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
|
||||
_, err = bot.Session.ChannelMessageSend(bot.RealtimeChannel.ID, message)
|
||||
if bot.RelayChannel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = bot.Session.ChannelMessageSend(bot.RelayChannel.ID, message)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,45 +12,40 @@ var (
|
||||
|
||||
// CalcSum32 calculates the custom MHF "sum32" checksum of the given data.
|
||||
func CalcSum32(data []byte) uint32 {
|
||||
tableIdx0 := int(len(data) & 0xFF)
|
||||
tableIdx1 := int(data[len(data)>>1] & 0xFF)
|
||||
|
||||
tableIdx0 := (len(data) + 1) & 0xFF
|
||||
tableIdx1 := int((data[len(data)>>1] + 1) & 0xFF)
|
||||
out := make([]byte, 4)
|
||||
for i := 0; i < len(data); i++ {
|
||||
tableIdx0++
|
||||
tableIdx1++
|
||||
|
||||
tmp := byte((_sum32Table1[tableIdx1%9] ^ _sum32Table0[tableIdx0%7]) ^ data[i])
|
||||
out[i&3] = (out[i&3] + tmp) & 0xFF
|
||||
key := data[i] ^ _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9]
|
||||
out[i&3] = (out[i&3] + key) & 0xFF
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint32(out)
|
||||
}
|
||||
|
||||
func rotate(k *uint32) {
|
||||
*k = uint32(((54323 * uint(*k)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
// EncryptBin8 encrypts the given data using MHF's "binary8" encryption.
|
||||
func EncryptBin8(data []byte, key byte) []byte {
|
||||
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
|
||||
|
||||
_key := uint32(key)
|
||||
var output []byte
|
||||
for i := 0; i < len(data); i++ {
|
||||
tmp := (_bin8Key[i&7] ^ byte((curKey>>13)&0xFF))
|
||||
rotate(&_key)
|
||||
tmp := _bin8Key[i&7] ^ byte((_key>>13)&0xFF)
|
||||
output = append(output, data[i]^tmp)
|
||||
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// DecryptBin8 decrypts the given MHF "binary8" data.
|
||||
func DecryptBin8(data []byte, key byte) []byte {
|
||||
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF)
|
||||
|
||||
_key := uint32(key)
|
||||
var output []byte
|
||||
for i := 0; i < len(data); i++ {
|
||||
tmp := (data[i] ^ byte((curKey>>13)&0xFF))
|
||||
rotate(&_key)
|
||||
tmp := data[i] ^ byte((_key>>13)&0xFF)
|
||||
output = append(output, tmp^_bin8Key[i&7])
|
||||
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func (s *Server) handleEntranceServerConnection(conn net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
}
|
||||
}
|
||||
|
||||
sid := (4096 + serverIdx*256) * 6000
|
||||
if si.IP == "" {
|
||||
si.IP = config.Host
|
||||
}
|
||||
@@ -38,8 +37,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
} else {
|
||||
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
|
||||
}
|
||||
bf.WriteUint16(16 + uint16(serverIdx))
|
||||
bf.WriteUint16(0x0000)
|
||||
bf.WriteUint16(uint16(serverIdx | 16))
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(uint16(len(si.Channels)))
|
||||
bf.WriteUint8(si.Type)
|
||||
bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3))
|
||||
@@ -47,20 +46,15 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
bf.WriteUint8(si.Recommended)
|
||||
}
|
||||
|
||||
if s.erupeConfig.RealClientMode <= _config.F5 {
|
||||
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
|
||||
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false))
|
||||
} else if s.erupeConfig.RealClientMode <= _config.GG {
|
||||
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
|
||||
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
bf.WriteUint8(uint8(len(combined)))
|
||||
bf.WriteBytes(combined)
|
||||
fullName := append(append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...), stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
if s.erupeConfig.RealClientMode >= _config.G1 && s.erupeConfig.RealClientMode <= _config.G5 {
|
||||
bf.WriteUint8(uint8(len(fullName)))
|
||||
bf.WriteBytes(fullName)
|
||||
} else {
|
||||
bf.WriteUint8(0) // Prevents malformed server name
|
||||
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...)
|
||||
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...)
|
||||
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false))
|
||||
if s.erupeConfig.RealClientMode >= _config.G51 {
|
||||
bf.WriteUint8(0) // Ignored
|
||||
}
|
||||
bf.WriteBytes(stringsupport.PaddedString(string(fullName), 65, false))
|
||||
}
|
||||
|
||||
if s.erupeConfig.RealClientMode >= _config.GG {
|
||||
@@ -68,31 +62,31 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
|
||||
}
|
||||
|
||||
for channelIdx, ci := range si.Channels {
|
||||
sid = (4096 + serverIdx*256) + (16 + channelIdx)
|
||||
if _config.ErupeConfig.DevMode && _config.ErupeConfig.ProxyPort != 0 {
|
||||
bf.WriteUint16(_config.ErupeConfig.ProxyPort)
|
||||
sid := (serverIdx<<8 | 4096) + (channelIdx | 16)
|
||||
if _config.ErupeConfig.DebugOptions.ProxyPort != 0 {
|
||||
bf.WriteUint16(_config.ErupeConfig.DebugOptions.ProxyPort)
|
||||
} else {
|
||||
bf.WriteUint16(ci.Port)
|
||||
}
|
||||
bf.WriteUint16(16 + uint16(channelIdx))
|
||||
bf.WriteUint16(uint16(channelIdx | 16))
|
||||
bf.WriteUint16(ci.MaxPlayers)
|
||||
var currentPlayers uint16
|
||||
s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(¤tPlayers)
|
||||
bf.WriteUint16(currentPlayers)
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(319) // Unk
|
||||
bf.WriteUint16(252) // Unk
|
||||
bf.WriteUint16(248) // Unk
|
||||
bf.WriteUint16(12345) // Unk
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(319) // Unk
|
||||
bf.WriteUint16(254 - currentPlayers) // Unk
|
||||
bf.WriteUint16(255 - currentPlayers) // Unk
|
||||
bf.WriteUint16(12345)
|
||||
}
|
||||
}
|
||||
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
|
||||
bf.WriteUint32(0x0000003C)
|
||||
bf.WriteUint32(60)
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
@@ -136,7 +130,7 @@ func makeSv2Resp(config *_config.Config, s *Server, local bool) []byte {
|
||||
}
|
||||
rawServerData := encodeServerInfo(config, s, local)
|
||||
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
if s.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(rawServerData), hex.Dump(rawServerData))
|
||||
}
|
||||
|
||||
@@ -161,14 +155,14 @@ func makeUsrResp(pkt []byte, s *Server) []byte {
|
||||
var sid uint16
|
||||
err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid)
|
||||
if err != nil {
|
||||
resp.WriteBytes(make([]byte, 4))
|
||||
resp.WriteUint16(0)
|
||||
} else {
|
||||
resp.WriteUint16(sid)
|
||||
resp.WriteUint16(0)
|
||||
}
|
||||
resp.WriteUint16(0)
|
||||
}
|
||||
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
if s.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(resp.Data()), hex.Dump(resp.Data()))
|
||||
}
|
||||
|
||||
|
||||
@@ -198,17 +198,17 @@ func (s *Server) checkToken(uid uint32) (bool, error) {
|
||||
}
|
||||
|
||||
func (s *Server) registerUidToken(uid uint32) (uint32, string, error) {
|
||||
token := token.Generate(16)
|
||||
_token := token.Generate(16)
|
||||
var tid uint32
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, token).Scan(&tid)
|
||||
return tid, token, err
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, _token).Scan(&tid)
|
||||
return tid, _token, err
|
||||
}
|
||||
|
||||
func (s *Server) registerPsnToken(psn string) (uint32, string, error) {
|
||||
token := token.Generate(16)
|
||||
_token := token.Generate(16)
|
||||
var tid uint32
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, token).Scan(&tid)
|
||||
return tid, token, err
|
||||
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, _token).Scan(&tid)
|
||||
return tid, _token, err
|
||||
}
|
||||
|
||||
func (s *Server) validateToken(token string, tokenID uint32) bool {
|
||||
@@ -229,9 +229,9 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
|
||||
var passDB string
|
||||
err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
s.logger.Info("User not found", zap.String("User", user))
|
||||
if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.AutoCreateAccount {
|
||||
if s.erupeConfig.AutoCreateAccount {
|
||||
uid, err = s.registerDBAccount(user, pass)
|
||||
if err == nil {
|
||||
return uid, SIGN_SUCCESS
|
||||
@@ -244,6 +244,15 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
|
||||
return 0, SIGN_EABORT
|
||||
} else {
|
||||
if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil {
|
||||
var bans int
|
||||
err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires IS NULL`, uid).Scan(&bans)
|
||||
if err == nil && bans > 0 {
|
||||
return uid, SIGN_EELIMINATE
|
||||
}
|
||||
err = s.db.QueryRow(`SELECT count(*) FROM bans WHERE user_id=$1 AND expires > now()`, uid).Scan(&bans)
|
||||
if err == nil && bans > 0 {
|
||||
return uid, SIGN_ESUSPEND
|
||||
}
|
||||
return uid, SIGN_SUCCESS
|
||||
}
|
||||
return 0, SIGN_EPASS
|
||||
|
||||
@@ -72,7 +72,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
bf.WriteUint32(char.ID)
|
||||
|
||||
// Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.MaxLauncherHR {
|
||||
if s.server.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
bf.WriteUint16(999)
|
||||
} else {
|
||||
bf.WriteUint16(char.HRP)
|
||||
@@ -144,20 +144,38 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
|
||||
s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
|
||||
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
|
||||
}
|
||||
bf.WriteUint16(0xCA10)
|
||||
bf.WriteUint16(0x4E20)
|
||||
ps.Uint16(bf, "", false) // unk key
|
||||
bf.WriteUint8(0x00)
|
||||
bf.WriteUint16(0xCA11)
|
||||
bf.WriteUint16(0x0001)
|
||||
bf.WriteUint16(0x4E20)
|
||||
ps.Uint16(bf, "", false) // unk ipv4
|
||||
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[0])
|
||||
if s.server.erupeConfig.DebugOptions.CapLink.Values[0] == 51728 {
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[1])
|
||||
if s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20002 {
|
||||
ps.Uint16(bf, s.server.erupeConfig.DebugOptions.CapLink.Key, false)
|
||||
}
|
||||
}
|
||||
caStruct := []struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint32
|
||||
Unk2 string
|
||||
}{}
|
||||
bf.WriteUint8(uint8(len(caStruct)))
|
||||
for i := range caStruct {
|
||||
bf.WriteUint8(caStruct[i].Unk0)
|
||||
bf.WriteUint32(caStruct[i].Unk1)
|
||||
ps.Uint8(bf, caStruct[i].Unk2, false)
|
||||
}
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[2])
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[3])
|
||||
bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[4])
|
||||
if s.server.erupeConfig.DebugOptions.CapLink.Values[2] == 51729 && s.server.erupeConfig.DebugOptions.CapLink.Values[3] == 1 && s.server.erupeConfig.DebugOptions.CapLink.Values[4] == 20000 {
|
||||
ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DebugOptions.CapLink.Host, s.server.erupeConfig.DebugOptions.CapLink.Port), false)
|
||||
}
|
||||
|
||||
bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix()))
|
||||
bf.WriteUint32(0)
|
||||
|
||||
tickets := []uint32{
|
||||
s.server.erupeConfig.GameplayOptions.MezfesSoloTickets,
|
||||
s.server.erupeConfig.GameplayOptions.MezfesGroupTickets,
|
||||
s.server.erupeConfig.GameplayOptions.MezFesSoloTickets,
|
||||
s.server.erupeConfig.GameplayOptions.MezFesGroupTickets,
|
||||
}
|
||||
stalls := []uint8{
|
||||
10, 3, 6, 9, 4, 8, 5, 7,
|
||||
|
||||
@@ -37,7 +37,7 @@ type Session struct {
|
||||
func (s *Session) work() {
|
||||
pkt, err := s.cryptConn.ReadPacket()
|
||||
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (s *Session) handlePacket(pkt []byte) error {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt)
|
||||
reqType := string(bf.ReadNullTerminatedBytes())
|
||||
switch reqType[:len(reqType)-3] {
|
||||
case "DLTSKEYSIGN:", "DSGN:":
|
||||
case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
|
||||
s.handleDSGN(bf)
|
||||
case "PS3SGN:":
|
||||
s.client = PS3
|
||||
@@ -78,7 +78,7 @@ func (s *Session) handlePacket(pkt []byte) error {
|
||||
}
|
||||
default:
|
||||
s.logger.Warn("Unknown request", zap.String("reqType", reqType))
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogInboundMessages {
|
||||
fmt.Printf("\n[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func (s *Session) authenticate(username string, password string) {
|
||||
default:
|
||||
bf.WriteUint8(uint8(resp))
|
||||
}
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
if s.server.erupeConfig.DebugOptions.LogOutboundMessages {
|
||||
fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data()))
|
||||
}
|
||||
_ = s.cryptConn.SendPacket(bf.Data())
|
||||
|
||||
@@ -80,7 +80,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
|
||||
PatchServer: s.erupeConfig.SignV2.PatchServer,
|
||||
Notices: []string{},
|
||||
}
|
||||
if s.erupeConfig.DevModeOptions.MaxLauncherHR {
|
||||
if s.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
for i := range resp.Characters {
|
||||
resp.Characters[i].HR = 7
|
||||
}
|
||||
@@ -93,8 +93,8 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
|
||||
ID: uint32(channelserver.TimeWeekStart().Unix()),
|
||||
Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()),
|
||||
End: uint32(channelserver.TimeWeekNext().Unix()),
|
||||
SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets,
|
||||
GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets,
|
||||
SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets,
|
||||
GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets,
|
||||
Stalls: stalls,
|
||||
}
|
||||
if !s.erupeConfig.HideLoginNotice {
|
||||
@@ -226,7 +226,7 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
if s.erupeConfig.DevModeOptions.MaxLauncherHR {
|
||||
if s.erupeConfig.DebugOptions.MaxLauncherHR {
|
||||
character.HR = 7
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
Reference in New Issue
Block a user