Merge branch 'main' into feature/warehouse-v2

This commit is contained in:
wish
2024-02-20 17:50:04 +11:00
84 changed files with 1894 additions and 3345 deletions

48
.github/workflows/docker.yml vendored Normal file
View 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
View File

@@ -7,3 +7,4 @@ savedata/*/
*.exe *.exe
*.lnk *.lnk
*.bat *.bat
/docker/db-data

29
AUTHORS.md Normal file
View 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
View 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", "." ]

View File

@@ -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. 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. 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 ## Resources
- [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z) - [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
View 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
}

View File

@@ -1,6 +1,7 @@
package mhfcourse package mhfcourse
import ( import (
_config "erupe-ce/config"
"math" "math"
"sort" "sort"
"time" "time"
@@ -66,12 +67,15 @@ func CourseExists(ID uint16, c []Course) bool {
// GetCourseStruct returns a slice of Course(s) from a rights integer // GetCourseStruct returns a slice of Course(s) from a rights integer
func GetCourseStruct(rights uint32) ([]Course, uint32) { 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() s := Courses()
sort.Slice(s, func(i, j int) bool { sort.Slice(s, func(i, j int) bool {
return s[i].ID > s[j].ID return s[i].ID > s[j].ID
}) })
var normalCafeCourseSet, netcafeCourseSet bool var normalCafeCourseSet, netcafeCourseSet, hidenCourseSet bool
for _, course := range s { for _, course := range s {
if rights-course.Value() < 0x80000000 { if rights-course.Value() < 0x80000000 {
switch course.ID { switch course.ID {
@@ -88,6 +92,12 @@ func GetCourseStruct(rights uint32) ([]Course, uint32) {
} }
netcafeCourseSet = true netcafeCourseSet = true
resp = append(resp, Course{ID: 30}) 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)) course.Expiry = time.Date(2030, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC+9", 9*60*60))
resp = append(resp, course) resp = append(resp, course)

View File

@@ -6,7 +6,6 @@ import (
// StringStack is a basic LIFO "stack" for storing strings. // StringStack is a basic LIFO "stack" for storing strings.
type StringStack struct { type StringStack struct {
Locked bool
stack []string stack []string
} }
@@ -20,20 +19,6 @@ func (s *StringStack) Set(v string) {
s.stack = []string{v} 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. // Push pushes a string onto the stack.
func (s *StringStack) Push(v string) { func (s *StringStack) Push(v string) {
s.stack = append(s.stack, v) s.stack = append(s.stack, v)

View File

@@ -13,29 +13,36 @@
"DeleteOnSaveCorruption": false, "DeleteOnSaveCorruption": false,
"ClientMode": "ZZ", "ClientMode": "ZZ",
"QuestCacheExpiry": 300, "QuestCacheExpiry": 300,
"ProxyPort": 0,
"CommandPrefix": "!", "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, "CleanDB": false,
"MaxLauncherHR": false, "MaxLauncherHR": false,
"LogInboundMessages": false, "LogInboundMessages": false,
"LogOutboundMessages": false, "LogOutboundMessages": false,
"LogMessageData": false, "LogMessageData": false,
"MaxHexdumpLength": 256, "MaxHexdumpLength": 256,
"DivaEvent": 0, "DivaOverride": 0,
"FestaEvent": -1, "FestaOverride": -1,
"TournamentEvent": 0, "TournamentOverride": 0,
"DisableTokenCheck": false, "DisableTokenCheck": false,
"QuestDebugTools": false, "QuestTools": false,
"EarthStatusOverride": 0, "AutoQuestBackport": true,
"EarthIDOverride": 0, "ProxyPort": 0,
"EarthMonsterOverride": [0, 0, 0, 0], "CapLink": {
"SaveDumps": { "Values": [51728, 20000, 51729, 1, 20000],
"Enabled": true, "Key": "",
"RawEnabled": false, "Host": "",
"OutputDir": "save-backups" "Port": 80
} }
}, },
"GameplayOptions": { "GameplayOptions": {
@@ -51,8 +58,6 @@
"ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]], "ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]],
"BonusQuestAllowance": 3, "BonusQuestAllowance": 3,
"DailyQuestAllowance": 1, "DailyQuestAllowance": 1,
"MezfesSoloTickets": 10,
"MezfesGroupTickets": 4,
"LowLatencyRaviente": false, "LowLatencyRaviente": false,
"RegularRavienteMaxPlayers": 8, "RegularRavienteMaxPlayers": 8,
"ViolentRavienteMaxPlayers": 8, "ViolentRavienteMaxPlayers": 8,
@@ -61,12 +66,29 @@
"SmallBerserkRavienteMaxPlayers": 8, "SmallBerserkRavienteMaxPlayers": 8,
"GUrgentRate": 0.10, "GUrgentRate": 0.10,
"GCPMultiplier": 1.00, "GCPMultiplier": 1.00,
"HRPMultiplier": 1.00,
"HRPMultiplierNC": 1.00,
"SRPMultiplier": 1.00,
"SRPMultiplierNC": 1.00,
"GRPMultiplier": 1.00, "GRPMultiplier": 1.00,
"GRPMultiplierNC": 1.00,
"GSRPMultiplier": 1.00, "GSRPMultiplier": 1.00,
"GSRPMultiplierNC": 1.00,
"ZennyMultiplier": 1.00,
"ZennyMultiplierNC": 1.00,
"GZennyMultiplier": 1.00, "GZennyMultiplier": 1.00,
"GZennyMultiplierNC": 1.00,
"MaterialMultiplier": 1.00, "MaterialMultiplier": 1.00,
"MaterialMultiplierNC": 1.00,
"GMaterialMultiplier": 1.00,
"GMaterialMultiplierNC": 1.00,
"ExtraCarves": 0, "ExtraCarves": 0,
"ExtraCarvesNC": 0,
"GExtraCarves": 0,
"GExtraCarvesNC": 0,
"DisableHunterNavi": false, "DisableHunterNavi": false,
"MezFesSoloTickets": 5,
"MezFesGroupTickets": 1,
"MezFesDuration": 172800, "MezFesDuration": 172800,
"MezFesSwitchMinigame": false, "MezFesSwitchMinigame": false,
"EnableKaijiEvent": false, "EnableKaijiEvent": false,
@@ -78,7 +100,11 @@
"Discord": { "Discord": {
"Enabled": false, "Enabled": false,
"BotToken": "", "BotToken": "",
"RealtimeChannelID": "" "RelayChannel": {
"Enabled": false,
"MaxMessageLength": 183,
"RelayChannelID": ""
}
}, },
"Commands": [ "Commands": [
{ {
@@ -121,6 +147,21 @@
"Enabled": true, "Enabled": true,
"Description": "Link a PlayStation Network ID to your account", "Description": "Link a PlayStation Network ID to your account",
"Prefix": "psn" "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": [ "Courses": [

View File

@@ -80,11 +80,14 @@ type Config struct {
ClientMode string ClientMode string
RealClientMode Mode RealClientMode Mode
QuestCacheExpiry int // Number of seconds to keep quest data cached 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 CommandPrefix string // The prefix for commands
DevMode bool AutoCreateAccount bool // Automatically create accounts if they don't exist
DefaultCourses []uint16
DevModeOptions DevModeOptions EarthStatus int32
EarthID int32
EarthMonsters []int32
SaveDumps SaveDumpOptions
DebugOptions DebugOptions
GameplayOptions GameplayOptions GameplayOptions GameplayOptions
Discord Discord Discord Discord
Commands []Command Commands []Command
@@ -96,30 +99,35 @@ type Config struct {
Entrance Entrance Entrance Entrance
} }
// DevModeOptions holds various debug/temporary options for use while developing Erupe. type SaveDumpOptions struct {
type DevModeOptions struct { Enabled bool
AutoCreateAccount bool // Automatically create accounts if they don't exist RawEnabled bool
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. 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. 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 LogInboundMessages bool // Log all messages sent to the server
LogOutboundMessages bool // Log all messages sent to the clients LogOutboundMessages bool // Log all messages sent to the clients
LogMessageData bool // Log all bytes transferred as a hexdump LogMessageData bool // Log all bytes transferred as a hexdump
MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
DivaEvent int // Diva Defense event status DivaOverride int // Diva Defense event status
FestaEvent int // Hunter's Festa event status FestaOverride int // Hunter's Festa event status
TournamentEvent int // VS Tournament event status TournamentOverride int // VS Tournament event status
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!) DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
QuestDebugTools bool // Enable various quest debug logs QuestTools bool // Enable various quest debug logs
EarthStatusOverride int32 AutoQuestBackport bool // Automatically backport quest files
EarthIDOverride int32 ProxyPort uint16 // Forces the game to connect to a channel server proxy
EarthMonsterOverride []int32 CapLink CapLinkOptions
SaveDumps SaveDumpOptions
} }
type SaveDumpOptions struct { type CapLinkOptions struct {
Enabled bool Values []uint16
RawEnabled bool Key string
OutputDir string Host string
Port int
} }
// GameplayOptions has various gameplay modifiers // GameplayOptions has various gameplay modifiers
@@ -137,8 +145,6 @@ type GameplayOptions struct {
ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members] ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members]
BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily
DailyQuestAllowance uint32 // Number of Daily 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 LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive
RegularRavienteMaxPlayers uint8 RegularRavienteMaxPlayers uint8
ViolentRavienteMaxPlayers uint8 ViolentRavienteMaxPlayers uint8
@@ -147,12 +153,29 @@ type GameplayOptions struct {
SmallBerserkRavienteMaxPlayers uint8 SmallBerserkRavienteMaxPlayers uint8
GUrgentRate float32 // Adjusts the rate of G Urgent quests spawning GUrgentRate float32 // Adjusts the rate of G Urgent quests spawning
GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion 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 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 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 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 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 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 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) MezFesDuration int // Seconds that MezFes will last for weekly (from 12AM Mon backwards)
MezFesSwitchMinigame bool // Swaps out Volpakkun Together for Tokotoko Partnya MezFesSwitchMinigame bool // Swaps out Volpakkun Together for Tokotoko Partnya
EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar
@@ -166,7 +189,13 @@ type GameplayOptions struct {
type Discord struct { type Discord struct {
Enabled bool Enabled bool
BotToken string BotToken string
RealtimeChannelID string RelayChannel DiscordRelay
}
type DiscordRelay struct {
Enabled bool
MaxMessageLength int
RelayChannelID string
} }
// Command is a channelserver chat command // Command is a channelserver chat command

66
docker/README.md Normal file
View 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
View 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
View 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

BIN
erupe.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

4
go.mod
View File

@@ -10,7 +10,7 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/spf13/viper v1.17.0 github.com/spf13/viper v1.17.0
go.uber.org/zap v1.26.0 go.uber.org/zap v1.26.0
golang.org/x/crypto v0.15.0 golang.org/x/crypto v0.17.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
) )
@@ -32,7 +32,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/net v0.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/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

8
go.sum
View File

@@ -220,8 +220,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= 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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -345,8 +345,8 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

21
main.go
View File

@@ -22,13 +22,10 @@ import (
) )
// Temporary DB auto clean on startup for quick development & testing. // 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 guild_characters")
_ = db.MustExec("DELETE FROM guilds") _ = db.MustExec("DELETE FROM guilds")
_ = db.MustExec("DELETE FROM characters") _ = db.MustExec("DELETE FROM characters")
if config.ProxyPort == 0 {
_ = db.MustExec("DELETE FROM sign_sessions")
}
_ = db.MustExec("DELETE FROM users") _ = db.MustExec("DELETE FROM users")
} }
@@ -48,11 +45,7 @@ func main() {
var zapLogger *zap.Logger var zapLogger *zap.Logger
config := _config.ErupeConfig config := _config.ErupeConfig
if config.DevMode {
zapLogger, _ = zap.NewDevelopment() zapLogger, _ = zap.NewDevelopment()
} else {
zapLogger, _ = zap.NewProduction()
}
defer zapLogger.Sync() defer zapLogger.Sync()
logger := zapLogger.Named("main") logger := zapLogger.Named("main")
@@ -98,6 +91,12 @@ func main() {
} }
discordBot = bot 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") logger.Info("Discord: Started successfully")
} else { } else {
logger.Info("Discord: Disabled") logger.Info("Discord: Disabled")
@@ -126,16 +125,16 @@ func main() {
logger.Info("Database: Started successfully") logger.Info("Database: Started successfully")
// Clear stale data // Clear stale data
if config.ProxyPort == 0 { if config.DebugOptions.ProxyPort == 0 {
_ = db.MustExec("DELETE FROM sign_sessions") _ = db.MustExec("DELETE FROM sign_sessions")
} }
_ = db.MustExec("DELETE FROM servers") _ = db.MustExec("DELETE FROM servers")
_ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`) _ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`)
// Clean the DB if the option is on. // Clean the DB if the option is on.
if config.DevMode && config.DevModeOptions.CleanDB { if config.DebugOptions.CleanDB {
logger.Info("Database: Started clearing...") logger.Info("Database: Started clearing...")
cleanDB(db, config) cleanDB(db)
logger.Info("Database: Finished clearing") logger.Info("Database: Finished clearing")
} }

View File

@@ -11,7 +11,8 @@ type ChatType uint8
// Chat types // Chat types
const ( const (
ChatTypeLocal ChatType = 1 ChatTypeWorld ChatType = 0
ChatTypeStage = 1
ChatTypeGuild = 2 ChatTypeGuild = 2
ChatTypeAlliance = 3 ChatTypeAlliance = 3
ChatTypeParty = 4 ChatTypeParty = 4

View File

@@ -4,11 +4,10 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
_config "erupe-ce/config" _config "erupe-ce/config"
"erupe-ce/network/crypto"
"fmt" "fmt"
"io" "io"
"net" "net"
"erupe-ce/network/crypto"
) )
// CryptConn represents a MHF encrypted two-way connection, // 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) 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 { 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("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) 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. // Attempt to bruteforce it.
fmt.Println("Crypto out of sync? Attempting bruteforce") fmt.Println("Crypto out of sync? Attempting bruteforce")
for key := byte(0); key < 255; key++ { 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)) //fmt.Printf("Key: 0x%X\n%s\n", key, hex.Dump(out))
if cph.Check0 == check0 && cph.Check1 == check1 && cph.Check2 == check2 { if cph.Check0 == check0 && cph.Check1 == check1 && cph.Check2 == check2 {
fmt.Printf("Bruceforce successful, override key: 0x%X\n", key) fmt.Printf("Bruceforce successful, override key: 0x%X\n", key)
@@ -106,7 +105,7 @@ func (cc *CryptConn) SendPacket(data []byte) error {
} }
// Encrypt the data // 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 := &CryptPacketHeader{}
header.Pf0 = byte(((uint(len(encData)) >> 12) & 0xF3) | 3) header.Pf0 = byte(((uint(len(encData)) >> 12) & 0xF3) | 3)
@@ -123,9 +122,7 @@ func (cc *CryptConn) SendPacket(data []byte) error {
return err return err
} }
cc.conn.Write(headerBytes) cc.conn.Write(append(headerBytes, encData...))
cc.conn.Write(encData)
cc.sentPackets++ cc.sentPackets++
cc.prevSendPacketCombinedCheck = combinedCheck cc.prevSendPacketCombinedCheck = combinedCheck

View File

@@ -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} _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. // Crypto is a generalized MHF crypto function that can perform both encryption and decryption,
// 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,
// these two crypto operations are combined into a single function because they shared most of their logic. // these two crypto operations are combined into a single function because they shared most of their logic.
// encrypt: cryptType==0 func Crypto(data []byte, rotKey uint32, encrypt bool, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) {
// decrypt: cryptType==1
func _generalCrypt(data []byte, rotKey uint32, cryptType int, overrideByteKey *byte) ([]byte, uint16, uint16, uint16, uint16) {
cryptKeyTruncByte := byte(((rotKey >> 1) % 999983) & 0xFF) cryptKeyTruncByte := byte(((rotKey >> 1) % 999983) & 0xFF)
if overrideByteKey != nil { if overrideByteKey != nil {
cryptKeyTruncByte = *overrideByteKey cryptKeyTruncByte = *overrideByteKey
} }
derivedCryptKey := int32((uint32(len(data)) * (uint32(cryptKeyTruncByte) + 1)) & 0xFFFFFFFF) derivedCryptKey := (uint32(len(data)) * (uint32(cryptKeyTruncByte) + 1)) & 0xFFFFFFFF
sharedBufIdx := byte(1) sharedBufIdx := byte(1)
accumulator0 := uint32(0) var accumulator0, accumulator1, accumulator2 uint32
accumulator1 := uint32(0)
accumulator2 := uint32(0)
var outputData []byte var outputData []byte
if cryptType == 0 { if encrypt {
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
// Do the encryption for this iteration // Do the encryption for this iteration
encKeyIdx := int32(((uint32(derivedCryptKey) >> 10) ^ uint32(data[i])) & 0xFF) encKeyIdx := ((derivedCryptKey >> 10) ^ uint32(data[i])) & 0xFF
derivedCryptKey = (0x4FD * (derivedCryptKey + 1)) derivedCryptKey = 1277*derivedCryptKey + 1277
encKeyByte := _encryptKey[encKeyIdx] encKeyByte := _encryptKey[encKeyIdx]
// Update the checksum accumulators. // Update the checksum accumulators.
accumulator2 = uint32((accumulator2 + (uint32(sharedBufIdx) * uint32(data[i]))) & 0xFFFFFFFF) accumulator2 = accumulator2 + (uint32(sharedBufIdx) * uint32(data[i]))
accumulator1 = uint32((accumulator1 + uint32(encKeyIdx)) & 0xFFFFFFFF) accumulator1 = accumulator1 + encKeyIdx
accumulator0 = uint32((accumulator0 + (uint32(encKeyByte)<<(i&7))&0xFFFFFFFF) & 0xFFFFFFFF) accumulator0 = accumulator0 + uint32(encKeyByte)<<(i&7)
// Append the output. // Append the output.
outputData = append(outputData, _sharedCryptKey[sharedBufIdx]^encKeyByte) 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. // Update the sharedBufIdx for the next iteration.
sharedBufIdx = data[i] sharedBufIdx = data[i]
} }
} else {
} else if cryptType == 1 {
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
// Do the decryption for this iteration // Do the decryption for this iteration
oldSharedBufIdx := sharedBufIdx oldSharedBufIdx := sharedBufIdx
tIdx := data[i] ^ _sharedCryptKey[sharedBufIdx] tIdx := data[i] ^ _sharedCryptKey[sharedBufIdx]
decKeyByte := _decryptKey[tIdx] decKeyByte := _decryptKey[tIdx]
sharedBufIdx = byte(((uint32(derivedCryptKey) >> 10) ^ uint32(decKeyByte)) & 0xFF) sharedBufIdx = byte((derivedCryptKey >> 10) ^ uint32(decKeyByte))
// Update the checksum accumulators. // Update the checksum accumulators.
accumulator0 = (accumulator0 + ((uint32(tIdx) << (i & 7)) & 0xFFFFFFFF)) accumulator0 = accumulator0 + uint32(tIdx)<<(i&7)
accumulator1 = (accumulator1 + uint32(decKeyByte)) & 0xFFFFFFFF accumulator1 = accumulator1 + uint32(decKeyByte)
accumulator2 = (accumulator2 + ((uint32(oldSharedBufIdx) * uint32(sharedBufIdx)) & 0xFFFFFFFF)) & 0xFFFFFFFF accumulator2 = accumulator2 + uint32(oldSharedBufIdx)*uint32(sharedBufIdx)
// Append the output. // Append the output.
outputData = append(outputData, sharedBufIdx) outputData = append(outputData, sharedBufIdx)
// Update the key pos for next iteration. // Update the key pos for next iteration.
derivedCryptKey = (0x4FD * (derivedCryptKey + 1)) derivedCryptKey = 1277*derivedCryptKey + 1277
} }
} }
combinedCheck := uint16((accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2)) & 0xFFFF) var check [4]uint16
check0 := uint16((accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16)) & 0xFFFF) check[0] = uint16(accumulator1 + (accumulator0 >> 1) + (accumulator2 >> 2))
check1 := uint16((accumulator1 ^ ((accumulator1 & 0xFFFF0000) >> 16)) & 0xFFFF) check[1] = uint16(accumulator0 ^ ((accumulator0 & 0xFFFF0000) >> 16))
check2 := uint16((accumulator2 ^ ((accumulator2 & 0xFFFF0000) >> 16)) & 0xFFFF) 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]
} }

View File

@@ -65,7 +65,7 @@ func TestEncrypt(t *testing.T) {
for k, tt := range tests { for k, tt := range tests {
testname := fmt.Sprintf("encrypt_test_%d", k) testname := fmt.Sprintf("encrypt_test_%d", k)
t.Run(testname, func(t *testing.T) { 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 { if cc != tt.ecc {
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc) t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
} else if c0 != tt.ec0 { } else if c0 != tt.ec0 {
@@ -86,7 +86,7 @@ func TestDecrypt(t *testing.T) {
for k, tt := range tests { for k, tt := range tests {
testname := fmt.Sprintf("decrypt_test_%d", k) testname := fmt.Sprintf("decrypt_test_%d", k)
t.Run(testname, func(t *testing.T) { 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 { if cc != tt.ecc {
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc) t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
} else if c0 != tt.ec0 { } else if c0 != tt.ec0 {

View File

@@ -3,9 +3,9 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfChargeFesta represents the MSG_MHF_CHARGE_FESTA // MsgMhfChargeFesta represents the MSG_MHF_CHARGE_FESTA
@@ -13,7 +13,8 @@ type MsgMhfChargeFesta struct {
AckHandle uint32 AckHandle uint32
FestaID uint32 FestaID uint32
GuildID uint32 GuildID uint32
Souls int Souls []uint16
Auto bool
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -26,11 +27,10 @@ func (m *MsgMhfChargeFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.FestaID = bf.ReadUint32() m.FestaID = bf.ReadUint32()
m.GuildID = bf.ReadUint32() m.GuildID = bf.ReadUint32()
m.Souls = 0
for i := bf.ReadUint16(); i > 0; i-- { for i := bf.ReadUint16(); i > 0; i-- {
m.Souls += int(bf.ReadUint16()) m.Souls = append(m.Souls, bf.ReadUint16())
} }
_ = bf.ReadUint8() // Unk m.Auto = bf.ReadBool()
return nil return nil
} }

BIN
rsrc_windows_amd64.syso Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -7,8 +7,8 @@ CREATE TABLE IF NOT EXISTS tower (
tsp INT, tsp INT,
block1 INT, block1 INT,
block2 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', skills TEXT,
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' gems TEXT
); );
ALTER TABLE IF EXISTS guild_characters ALTER TABLE IF EXISTS guild_characters

View 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;

View 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;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE users ADD COLUMN IF NOT EXISTS timer bool;
END;

View 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;

View 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;

View File

@@ -32,7 +32,7 @@ func stubEnumerateNoResults(s *Session, ackHandle uint32) {
func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) { func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) {
bf := byteframe.NewByteFrame() 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(0) bf.WriteUint32(0)
bf.WriteUint32(uint32(len(data))) bf.WriteUint32(uint32(len(data)))
@@ -128,7 +128,7 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLogin) pkt := p.(*mhfpacket.MsgSysLogin)
if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { if !s.server.erupeConfig.DebugOptions.DisableTokenCheck {
var token string 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) 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 { if err != nil {
@@ -1148,9 +1148,9 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride) bf.WriteInt32(s.server.erupeConfig.EarthStatus)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride) bf.WriteInt32(s.server.erupeConfig.EarthID)
for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride { for i, m := range s.server.erupeConfig.EarthMonsters {
if _config.ErupeConfig.RealClientMode <= _config.G9 { if _config.ErupeConfig.RealClientMode <= _config.G9 {
if i == 3 { if i == 3 {
break break

View File

@@ -94,7 +94,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
} }
bf.WriteUint32(cafeTime) // Total cafe time bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0) 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()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -1,8 +1,10 @@
package channelserver package channelserver
import ( import (
"crypto/rand"
"encoding/hex" "encoding/hex"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/mhfcid"
"erupe-ce/common/mhfcourse" "erupe-ce/common/mhfcourse"
"erupe-ce/common/token" "erupe-ce/common/token"
"erupe-ce/config" "erupe-ce/config"
@@ -58,7 +60,7 @@ func init() {
} }
func sendDisabledCommandMessage(s *Session, cmd _config.Command) { 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) { func sendServerChatMessage(s *Session, message string) {
@@ -86,28 +88,97 @@ func sendServerChatMessage(s *Session, message string) {
func parseChatCommand(s *Session, command string) { func parseChatCommand(s *Session, command string) {
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ") args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
switch args[0] { 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: case commands["PSN"].Prefix:
if commands["PSN"].Enabled { if commands["PSN"].Enabled || s.isOp() {
if len(args) > 1 { if len(args) > 1 {
var exists int var exists int
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists) s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
if exists == 0 { 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) _, 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 { 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 { } else {
sendServerChatMessage(s, s.server.dict["commandPSNExists"]) sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
} }
} else { } 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 { } else {
sendDisabledCommandMessage(s, commands["PSN"]) sendDisabledCommandMessage(s, commands["PSN"])
} }
case commands["Reload"].Prefix: case commands["Reload"].Prefix:
if commands["Reload"].Enabled { if commands["Reload"].Enabled || s.isOp() {
sendServerChatMessage(s, s.server.dict["commandReload"]) sendServerChatMessage(s, s.server.i18n.commands.reload)
var temp mhfpacket.MHFPacket var temp mhfpacket.MHFPacket
deleteNotif := byteframe.NewByteFrame() deleteNotif := byteframe.NewByteFrame()
for _, object := range s.stage.objects { for _, object := range s.stage.objects {
@@ -167,21 +238,21 @@ func parseChatCommand(s *Session, command string) {
sendDisabledCommandMessage(s, commands["Reload"]) sendDisabledCommandMessage(s, commands["Reload"])
} }
case commands["KeyQuest"].Prefix: case commands["KeyQuest"].Prefix:
if commands["KeyQuest"].Enabled { if commands["KeyQuest"].Enabled || s.isOp() {
if s.server.erupeConfig.RealClientMode < _config.G10 { if s.server.erupeConfig.RealClientMode < _config.G10 {
sendServerChatMessage(s, s.server.dict["commandKqfVersion"]) sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
} else { } else {
if len(args) > 1 { if len(args) > 1 {
if args[1] == "get" { 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" { } else if args[1] == "set" {
if len(args) > 2 && len(args[2]) == 16 { if len(args) > 2 && len(args[2]) == 16 {
hexd, _ := hex.DecodeString(args[2]) hexd, _ := hex.DecodeString(args[2])
s.kqf = hexd s.kqf = hexd
s.kqfOverride = true s.kqfOverride = true
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"]) sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success)
} else { } 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"]) sendDisabledCommandMessage(s, commands["KeyQuest"])
} }
case commands["Rights"].Prefix: case commands["Rights"].Prefix:
if commands["Rights"].Enabled { if commands["Rights"].Enabled || s.isOp() {
if len(args) > 1 { if len(args) > 1 {
v, _ := strconv.Atoi(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) _, 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 { if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v)) sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
} else { } 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 { } 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 { } else {
sendDisabledCommandMessage(s, commands["Rights"]) sendDisabledCommandMessage(s, commands["Rights"])
} }
case commands["Course"].Prefix: case commands["Course"].Prefix:
if commands["Course"].Enabled { if commands["Course"].Enabled || s.isOp() {
if len(args) > 1 { if len(args) > 1 {
for _, course := range mhfcourse.Courses() { for _, course := range mhfcourse.Courses() {
for _, alias := range course.Aliases() { for _, alias := range course.Aliases() {
@@ -224,11 +295,11 @@ func parseChatCommand(s *Session, command string) {
}) })
if ei != -1 { if ei != -1 {
delta = uint32(-1 * math.Pow(2, float64(course.ID))) 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 { } else {
delta = uint32(math.Pow(2, float64(course.ID))) 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) 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 { if err == nil {
@@ -236,71 +307,71 @@ func parseChatCommand(s *Session, command string) {
} }
updateRights(s) updateRights(s)
} else { } 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 return
} }
} }
} }
} else { } 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 { } else {
sendDisabledCommandMessage(s, commands["Course"]) sendDisabledCommandMessage(s, commands["Course"])
} }
case commands["Raviente"].Prefix: case commands["Raviente"].Prefix:
if commands["Raviente"].Enabled { if commands["Raviente"].Enabled || s.isOp() {
if len(args) > 1 { if len(args) > 1 {
if s.server.getRaviSemaphore() != nil { if s.server.getRaviSemaphore() != nil {
switch args[1] { switch args[1] {
case "start": case "start":
if s.server.raviente.register[1] == 0 { if s.server.raviente.register[1] == 0 {
s.server.raviente.register[1] = s.server.raviente.register[3] 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() s.notifyRavi()
} else { } else {
sendServerChatMessage(s, s.server.dict["commandRaviStartError"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
} }
case "cm", "check", "checkmultiplier", "multiplier": 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": case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
if s.server.erupeConfig.RealClientMode == _config.ZZ { if s.server.erupeConfig.RealClientMode == _config.ZZ {
switch args[1] { switch args[1] {
case "sr", "sendres", "resurrection": case "sr", "sendres", "resurrection":
if s.server.raviente.state[28] > 0 { 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 s.server.raviente.state[28] = 0
} else { } else {
sendServerChatMessage(s, s.server.dict["commandRaviResError"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
} }
case "ss", "sendsed": case "ss", "sendsed":
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
// Total BerRavi HP // 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] 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 s.server.raviente.support[1] = HP
case "rs", "reqsed": case "rs", "reqsed":
sendServerChatMessage(s, s.server.dict["commandRaviRequest"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.request)
// Total BerRavi HP // 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] 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 s.server.raviente.support[1] = HP + 1
} }
} else { } else {
sendServerChatMessage(s, s.server.dict["commandRaviVersion"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
} }
default: default:
sendServerChatMessage(s, s.server.dict["commandRaviError"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
} }
} else { } else {
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
} }
} else { } else {
sendServerChatMessage(s, s.server.dict["commandRaviError"]) sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
} }
} else { } else {
sendDisabledCommandMessage(s, commands["Raviente"]) sendDisabledCommandMessage(s, commands["Raviente"])
} }
case commands["Teleport"].Prefix: case commands["Teleport"].Prefix:
if commands["Teleport"].Enabled { if commands["Teleport"].Enabled || s.isOp() {
if len(args) > 2 { if len(args) > 2 {
x, _ := strconv.ParseInt(args[1], 10, 16) x, _ := strconv.ParseInt(args[1], 10, 16)
y, _ := strconv.ParseInt(args[2], 10, 16) y, _ := strconv.ParseInt(args[2], 10, 16)
@@ -315,17 +386,31 @@ func parseChatCommand(s *Session, command string) {
MessageType: BinaryMessageTypeState, MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes, 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 { } 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 { } else {
sendDisabledCommandMessage(s, commands["Teleport"]) 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: case commands["Help"].Prefix:
if commands["Help"].Enabled { if commands["Help"].Enabled || s.isOp() {
for _, command := range commands { 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)) 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 pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 { if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
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.ReadBytes(9)
tmp.SetLE() tmp.SetLE()
frame := tmp.ReadUint32() 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)) 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 { if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
// This is only correct most of the time // This is only correct most of the time
tmp.ReadBytes(20) tmp.ReadBytes(20)
@@ -362,24 +451,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
// Parse out the real casted binary payload // Parse out the real casted binary payload
var msgBinTargeted *binpacket.MsgBinTargeted var msgBinTargeted *binpacket.MsgBinTargeted
var authorLen, msgLen uint16 var message, author string
var msg []byte var returnToSender bool
isDiceCommand := false
if pkt.MessageType == BinaryMessageTypeChat { if pkt.MessageType == BinaryMessageTypeChat {
tmp.SetLE() tmp.SetLE()
tmp.Seek(int64(0), 0) tmp.Seek(8, 0)
_ = tmp.ReadUint32() message = string(tmp.ReadNullTerminatedBytes())
authorLen = tmp.ReadUint16() author = string(tmp.ReadNullTerminatedBytes())
msgLen = tmp.ReadUint16()
msg = tmp.ReadNullTerminatedBytes()
} }
// Customise payload // Customise payload
realPayload := pkt.RawDataPayload realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted { if pkt.BroadcastType == BroadcastTypeTargeted {
tmp.SetBE() tmp.SetBE()
tmp.Seek(int64(0), 0) tmp.Seek(0, 0)
msgBinTargeted = &binpacket.MsgBinTargeted{} msgBinTargeted = &binpacket.MsgBinTargeted{}
err := msgBinTargeted.Parse(tmp) err := msgBinTargeted.Parse(tmp)
if err != nil { if err != nil {
@@ -388,18 +473,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
} }
realPayload = msgBinTargeted.RawDataPayload realPayload = msgBinTargeted.RawDataPayload
} else if pkt.MessageType == BinaryMessageTypeChat { } else if pkt.MessageType == BinaryMessageTypeChat {
if msgLen == 6 && string(msg) == "@dice" { if message == "@dice" {
isDiceCommand = true returnToSender = true
roll := byteframe.NewByteFrame() m := binpacket.MsgBinChat{
roll.WriteInt16(1) // Unk Type: BinaryMessageTypeChat,
roll.SetLE() Flags: 4,
roll.WriteUint16(4) // Unk Message: fmt.Sprintf(`%d`, token.RNG().Intn(100)+1),
roll.WriteUint16(authorLen) SenderName: author,
dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1) }
roll.WriteUint16(uint16(len(dice) + 1)) bf := byteframe.NewByteFrame()
roll.WriteNullTerminatedBytes([]byte(dice)) bf.SetLE()
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes()) m.Build(bf)
realPayload = roll.Data() realPayload = bf.Data()
} else { } else {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.SetLE() bf.SetLE()
@@ -428,8 +513,8 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
case BroadcastTypeWorld: case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s, nil) s.server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage: case BroadcastTypeStage:
if isDiceCommand { if returnToSender {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller s.stage.BroadcastMHF(resp, nil)
} else { } else {
s.stage.BroadcastMHF(resp, s) s.stage.BroadcastMHF(resp, s)
} }

View File

@@ -97,6 +97,17 @@ func getPointers() map[SavePointer]int {
pointers[pGalleryData] = 72064 pointers[pGalleryData] = 72064
pointers[pGardenData] = 74424 pointers[pGardenData] = 74424
pointers[pRP] = 74614 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 { if _config.ErupeConfig.RealClientMode == _config.G5 {
pointers[lBookshelfData] = 5548 pointers[lBookshelfData] = 5548
@@ -212,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.Gender = false save.Gender = false
} }
if !save.IsNewCharacter { 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.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.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]

View File

@@ -1,6 +1,7 @@
package channelserver package channelserver
import ( import (
"erupe-ce/common/mhfmon"
"erupe-ce/common/stringsupport" "erupe-ce/common/stringsupport"
_config "erupe-ce/config" _config "erupe-ce/config"
"fmt" "fmt"
@@ -45,7 +46,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }
if s.server.erupeConfig.DevModeOptions.SaveDumps.RawEnabled { if s.server.erupeConfig.SaveDumps.RawEnabled {
dumpSaveData(s, saveData, "raw-savedata") dumpSaveData(s, saveData, "raw-savedata")
} }
s.logger.Info("Updating save with blob") s.logger.Info("Updating save with blob")
@@ -112,11 +113,11 @@ func grpToGR(n int) uint16 {
} }
func dumpSaveData(s *Session, data []byte, suffix string) { func dumpSaveData(s *Session, data []byte, suffix string) {
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled { if !s.server.erupeConfig.SaveDumps.Enabled {
return return
} else { } else {
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d", s.charID)) dir := filepath.Join(s.server.erupeConfig.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)) 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) _, err := os.Stat(dir)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -1042,34 +1043,34 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) {
{1105, 1, 10, 500, 0, 0, 0}, {1105, 1, 10, 500, 0, 0, 0},
{1105, 2, 10, 500, 0, 0, 0}, {1105, 2, 10, 500, 0, 0, 0},
// setServerBoss // setServerBoss
{2001, 1, 17, 58, 0, 6, 700}, {2001, 1, mhfmon.Gravios, 58, 0, 6, 700},
{2001, 1, 20, 58, 0, 3, 200}, {2001, 1, mhfmon.Gypceros, 58, 0, 3, 200},
{2001, 1, 22, 58, 0, 7, 250}, {2001, 1, mhfmon.Basarios, 58, 0, 7, 250},
{2001, 1, 27, 58, 0, 1, 100}, {2001, 1, mhfmon.Velocidrome, 58, 0, 1, 100},
{2001, 1, 53, 58, 0, 8, 1000}, {2001, 1, mhfmon.Rajang, 58, 0, 8, 1000},
{2001, 1, 67, 58, 0, 9, 500}, {2001, 1, mhfmon.ShogunCeanataur, 58, 0, 9, 500},
{2001, 1, 68, 58, 0, 2, 150}, {2001, 1, mhfmon.Bulldrome, 58, 0, 2, 150},
{2001, 1, 74, 58, 0, 4, 200}, {2001, 1, mhfmon.Hypnocatrice, 58, 0, 4, 200},
{2001, 1, 75, 58, 0, 5, 500}, {2001, 1, mhfmon.Lavasioth, 58, 0, 5, 500},
{2001, 1, 76, 58, 0, 10, 800}, {2001, 1, mhfmon.Tigrex, 58, 0, 10, 800},
{2001, 1, 80, 58, 0, 11, 900}, {2001, 1, mhfmon.Espinas, 58, 0, 11, 900},
{2001, 1, 89, 58, 0, 12, 600}, {2001, 1, mhfmon.Pariapuria, 58, 0, 12, 600},
{2001, 2, 17, 60, 0, 6, 700}, {2001, 2, mhfmon.Gravios, 60, 0, 6, 700},
{2001, 2, 20, 60, 0, 3, 200}, {2001, 2, mhfmon.Gypceros, 60, 0, 3, 200},
{2001, 2, 22, 60, 0, 7, 350}, {2001, 2, mhfmon.Basarios, 60, 0, 7, 350},
{2001, 2, 27, 60, 0, 1, 100}, {2001, 2, mhfmon.Velocidrome, 60, 0, 1, 100},
{2001, 2, 39, 60, 0, 13, 200}, {2001, 2, mhfmon.PurpleGypceros, 60, 0, 13, 200},
{2001, 2, 40, 60, 0, 15, 600}, {2001, 2, mhfmon.YianGaruga, 60, 0, 15, 600},
{2001, 2, 53, 60, 0, 8, 1000}, {2001, 2, mhfmon.Rajang, 60, 0, 8, 1000},
{2001, 2, 67, 60, 0, 2, 500}, {2001, 2, mhfmon.ShogunCeanataur, 60, 0, 2, 500},
{2001, 2, 68, 60, 0, 9, 150}, {2001, 2, mhfmon.Bulldrome, 60, 0, 9, 150},
{2001, 2, 74, 60, 0, 4, 200}, {2001, 2, mhfmon.Hypnocatrice, 60, 0, 4, 200},
{2001, 2, 75, 60, 0, 5, 500}, {2001, 2, mhfmon.Lavasioth, 60, 0, 5, 500},
{2001, 2, 76, 60, 0, 10, 800}, {2001, 2, mhfmon.Tigrex, 60, 0, 10, 800},
{2001, 2, 80, 60, 0, 11, 900}, {2001, 2, mhfmon.Espinas, 60, 0, 11, 900},
{2001, 2, 81, 60, 0, 14, 900}, {2001, 2, mhfmon.BurningEspinas, 60, 0, 14, 900},
{2001, 2, 89, 60, 0, 12, 600}, {2001, 2, mhfmon.Pariapuria, 60, 0, 12, 600},
{2001, 2, 94, 60, 0, 16, 1000}, {2001, 2, mhfmon.Dyuragaua, 60, 0, 16, 1000},
} }
case 6: case 6:
paperData = []PaperData{ paperData = []PaperData{

View File

@@ -3,6 +3,7 @@ package channelserver
import ( import (
"fmt" "fmt"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"golang.org/x/crypto/bcrypt"
"sort" "sort"
"strings" "strings"
"unicode" "unicode"
@@ -66,10 +67,56 @@ func getCharacterList(s *Server) string {
return message 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. // onDiscordMessage handles receiving messages from discord and forwarding them ingame.
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) { func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore messages from our bot, or ones that are not in the correct channel. // Ignore messages from bots, or messages that are not in the correct channel.
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID { if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RelayChannel.RelayChannelID {
return return
} }
@@ -79,11 +126,24 @@ func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCre
} }
return r return r
}, m.Author.Username)) }, m.Author.Username))
for i := 0; i < 8-len(m.Author.Username); i++ { for i := 0; i < 8-len(m.Author.Username); i++ {
paddedName += " " paddedName += " "
} }
message := s.discordBot.NormalizeDiscordMessage(fmt.Sprintf("[D] %s > %s", paddedName, m.Content))
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content) if len(message) > s.erupeConfig.Discord.RelayChannel.MaxMessageLength {
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message)) return
}
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])
}
} }

View File

@@ -70,8 +70,8 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
} }
var timestamps []uint32 var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 { if s.server.erupeConfig.DebugOptions.DivaOverride >= 0 {
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 { if s.server.erupeConfig.DebugOptions.DivaOverride == 0 {
if s.server.erupeConfig.RealClientMode >= _config.Z2 { if s.server.erupeConfig.RealClientMode >= _config.Z2 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
} else { } else {
@@ -79,7 +79,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
} }
return return
} }
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true) timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.DivaOverride), true)
} else { } else {
timestamps = generateDivaTimestamps(s, start, false) timestamps = generateDivaTimestamps(s, start, false)
} }

View File

@@ -36,7 +36,7 @@ func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking) pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.TournamentEvent state := s.server.erupeConfig.DebugOptions.TournamentOverride
// Unk // Unk
// Unk // Unk
// Start? // Start?
@@ -95,8 +95,9 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
func cleanupFesta(s *Session) { func cleanupFesta(s *Session) {
s.server.db.Exec("DELETE FROM events WHERE event_type='festa'") 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_registrations")
s.server.db.Exec("DELETE FROM festa_submissions")
s.server.db.Exec("DELETE FROM festa_prizes_accepted") 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 { func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
@@ -147,7 +148,7 @@ type FestaTrial struct {
TimesReq uint16 `db:"times_req"` TimesReq uint16 `db:"times_req"`
Locale uint16 `db:"locale_req"` Locale uint16 `db:"locale_req"`
Reward uint16 `db:"reward"` Reward uint16 `db:"reward"`
Monopoly FestivalColour `db:"monopoly"` Monopoly FestivalColor `db:"monopoly"`
Unk uint16 Unk uint16
} }
@@ -173,12 +174,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
} }
var timestamps []uint32 var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 { if s.server.erupeConfig.DebugOptions.FestaOverride >= 0 {
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 { if s.server.erupeConfig.DebugOptions.FestaOverride == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true) timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaOverride), true)
} else { } else {
timestamps = generateFestaTimestamps(s, start, false) timestamps = generateFestaTimestamps(s, start, false)
} }
@@ -233,9 +234,11 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(trial.TimesReq) bf.WriteUint16(trial.TimesReq)
bf.WriteUint16(trial.Locale) bf.WriteUint16(trial.Locale)
bf.WriteUint16(trial.Reward) bf.WriteUint16(trial.Reward)
bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly])) bf.WriteInt16(FestivalColorCodes[trial.Monopoly])
if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0
bf.WriteUint16(trial.Unk) bf.WriteUint16(trial.Unk)
} }
}
// The Winner and Loser Armor IDs are missing // The Winner and Loser Armor IDs are missing
// Item 7011 may not exist in older versions, remove to prevent crashes // Item 7011 may not exist in older versions, remove to prevent crashes
@@ -291,26 +294,49 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
} }
bf.WriteUint16(500) bf.WriteUint16(500)
categoryWinners := uint16(0) // NYI var temp uint32
bf.WriteUint16(categoryWinners) bf.WriteUint16(4)
for i := uint16(0); i < categoryWinners; i++ { for i := uint16(0); i < 4; i++ {
bf.WriteUint32(0) // Guild ID var guildID uint32
bf.WriteUint16(i + 1) // Category ID var guildName string
bf.WriteUint16(0) // Festa Team var guildTeam = FestivalColorNone
ps.Uint8(bf, "", true) // Guild Name s.server.db.QueryRow(`
} SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
FROM festa_submissions fs
dailyWinners := uint16(0) // NYI LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id
bf.WriteUint16(dailyWinners) LEFT JOIN guilds g ON fs.guild_id = g.id
for i := uint16(0); i < dailyWinners; i++ { WHERE fs.trial_type = $1
bf.WriteUint32(0) // Guild ID GROUP BY fs.guild_id, g.name, fr.team
bf.WriteUint16(i + 1) // Category ID ORDER BY _ DESC LIMIT 1
bf.WriteUint16(0) // Festa Team `, i+1).Scan(&guildID, &guildName, &guildTeam, &temp)
ps.Uint8(bf, "", true) // Guild Name 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 // Final bonus rates
bf.WriteUint32(1) // 5000-Infinity?
bf.WriteUint32(5000) // 5000+ souls bf.WriteUint32(5000) // 5000+ souls
bf.WriteUint32(2000) // 2000-4999 souls bf.WriteUint32(2000) // 2000-4999 souls
bf.WriteUint32(1000) // 1000-1999 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(100) // Normal rate
bf.WriteUint16(50) // 50% penalty bf.WriteUint16(50) // 50% penalty
if _config.ErupeConfig.RealClientMode >= _config.G52 {
ps.Uint16(bf, "", false) ps.Uint16(bf, "", false)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -349,7 +377,6 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBool(false) bf.WriteBool(false)
bf.WriteBool(true) bf.WriteBool(true)
} }
bf.WriteUint16(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -364,18 +391,18 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
if err != nil || guild == nil || applicant { if err != nil || guild == nil || applicant {
resp.WriteUint32(0) resp.WriteUint32(0)
resp.WriteUint32(0) resp.WriteInt32(0)
resp.WriteUint32(0xFFFFFFFF) resp.WriteInt32(-1)
resp.WriteUint32(0) resp.WriteInt32(0)
resp.WriteUint32(0) resp.WriteInt32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return return
} }
resp.WriteUint32(guild.Souls) resp.WriteUint32(guild.Souls)
resp.WriteUint32(1) // unk resp.WriteInt32(1) // unk
resp.WriteUint32(1) // unk resp.WriteInt32(1) // unk, rank?
resp.WriteUint32(1) // unk, rank? resp.WriteInt32(1) // unk
resp.WriteUint32(1) // unk resp.WriteInt32(1) // unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} }
@@ -391,16 +418,27 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(members)))
bf.WriteUint16(0) // Unk
sort.Slice(members, func(i, j int) bool { sort.Slice(members, func(i, j int) bool {
return members[i].Souls > members[j].Souls return members[i].Souls > members[j].Souls
}) })
var validMembers []*GuildMember
for _, member := range members { 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.CharID)
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
bf.WriteUint16(uint16(member.Souls))
bf.WriteUint16(0)
} else {
bf.WriteUint32(member.Souls) bf.WriteUint32(member.Souls)
} }
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -431,7 +469,14 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta) 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)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }

View File

@@ -21,18 +21,18 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
type FestivalColour string type FestivalColor string
const ( const (
FestivalColourNone FestivalColour = "none" FestivalColorNone FestivalColor = "none"
FestivalColourBlue FestivalColour = "blue" FestivalColorBlue FestivalColor = "blue"
FestivalColourRed FestivalColour = "red" FestivalColorRed FestivalColor = "red"
) )
var FestivalColourCodes = map[FestivalColour]int8{ var FestivalColorCodes = map[FestivalColor]int16{
FestivalColourNone: -1, FestivalColorNone: -1,
FestivalColourBlue: 0, FestivalColorBlue: 0,
FestivalColourRed: 1, FestivalColorRed: 1,
} }
type GuildApplicationType string type GuildApplicationType string
@@ -60,7 +60,7 @@ type Guild struct {
PugiOutfit3 uint8 `db:"pugi_outfit_3"` PugiOutfit3 uint8 `db:"pugi_outfit_3"`
PugiOutfits uint32 `db:"pugi_outfits"` PugiOutfits uint32 `db:"pugi_outfits"`
Recruiting bool `db:"recruiting"` Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"` FestivalColor FestivalColor `db:"festival_colour"`
Souls uint32 `db:"souls"` Souls uint32 `db:"souls"`
AllianceID uint32 `db:"alliance_id"` AllianceID uint32 `db:"alliance_id"`
Icon *GuildIcon `db:"icon"` Icon *GuildIcon `db:"icon"`
@@ -967,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(uint8(len(guildLeaderName))) bf.WriteUint8(uint8(len(guildLeaderName)))
bf.WriteBytes(guildName) bf.WriteBytes(guildName)
bf.WriteBytes(guildComment) bf.WriteBytes(guildComment)
bf.WriteInt8(FestivalColourCodes[guild.FestivalColour]) bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor]))
bf.WriteUint32(guild.RankRP) bf.WriteUint32(guild.RankRP)
bf.WriteBytes(guildLeaderName) bf.WriteBytes(guildLeaderName)
bf.WriteUint32(0) // Unk bf.WriteUint32(0) // Unk
@@ -1427,7 +1427,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint16(guild.MemberCount) bf.WriteUint16(uint16(len(guildMembers)))
sort.Slice(guildMembers[:], func(i, j int) bool { sort.Slice(guildMembers[:], func(i, j int) bool {
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
@@ -1460,7 +1460,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
} }
if guild.AllianceID > 0 { if guild.AllianceID > 0 {
bf.WriteUint16(alliance.TotalMembers - guild.MemberCount) bf.WriteUint16(alliance.TotalMembers - uint16(len(guildMembers)))
if guild.ID != alliance.ParentGuildID { if guild.ID != alliance.ParentGuildID {
mems, err := GetGuildMembers(s, alliance.ParentGuildID, false) mems, err := GetGuildMembers(s, alliance.ParentGuildID, false)
if err != nil { if err != nil {

View File

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

View File

@@ -60,9 +60,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
mail := &Mail{ mail := &Mail{
SenderID: s.charID, SenderID: s.charID,
RecipientID: pkt.CharID, RecipientID: pkt.CharID,
Subject: s.server.dict["guildInviteName"], Subject: s.server.i18n.guild.invite.title,
Body: fmt.Sprintf( Body: fmt.Sprintf(
s.server.dict["guildInvite"], s.server.i18n.guild.invite.body,
guildInfo.Name, guildInfo.Name,
), ),
IsGuildInvite: true, IsGuildInvite: true,
@@ -146,30 +146,30 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
err = guild.AcceptApplication(s, s.charID) err = guild.AcceptApplication(s, s.charID)
mail = append(mail, Mail{ mail = append(mail, Mail{
RecipientID: s.charID, RecipientID: s.charID,
Subject: s.server.dict["guildInviteSuccessName"], Subject: s.server.i18n.guild.invite.success.title,
Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name), Body: fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
}) })
mail = append(mail, Mail{ mail = append(mail, Mail{
SenderID: s.charID, SenderID: s.charID,
RecipientID: pkt.LeaderID, RecipientID: pkt.LeaderID,
Subject: s.server.dict["guildInviteAcceptedName"], Subject: s.server.i18n.guild.invite.accepted.title,
Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name), Body: fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
}) })
} else { } else {
err = guild.RejectApplication(s, s.charID) err = guild.RejectApplication(s, s.charID)
mail = append(mail, Mail{ mail = append(mail, Mail{
RecipientID: s.charID, RecipientID: s.charID,
Subject: s.server.dict["guildInviteRejectName"], Subject: s.server.i18n.guild.invite.rejected.title,
Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name), Body: fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
}) })
mail = append(mail, Mail{ mail = append(mail, Mail{
SenderID: s.charID, SenderID: s.charID,
RecipientID: pkt.LeaderID, RecipientID: pkt.LeaderID,
Subject: s.server.dict["guildInviteDeclined"], Subject: s.server.i18n.guild.invite.declined.title,
Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name), Body: fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
}) })
} }

View File

@@ -42,7 +42,7 @@ func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysPositionObject) 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) 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() s.stage.Lock()

View File

@@ -2,6 +2,7 @@ package channelserver
import ( import (
"database/sql" "database/sql"
"encoding/binary"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/decryption" "erupe-ce/common/decryption"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
@@ -16,11 +17,82 @@ import (
"go.uber.org/zap" "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) { func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetFile) pkt := p.(*mhfpacket.MsgSysGetFile)
if pkt.IsScenario { if pkt.IsScenario {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode { if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug( s.logger.Debug(
"Scenario", "Scenario",
zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID), zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID),
@@ -40,7 +112,7 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
} }
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
} else { } else {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode { if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug( s.logger.Debug(
"Quest", "Quest",
zap.String("Filename", pkt.Filename), zap.String("Filename", pkt.Filename),
@@ -58,6 +130,9 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
return return
} }
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
data = BackportQuest(decryption.UnpackSimple(data))
}
doAckBufSucceed(s, pkt.AckHandle, data) doAckBufSucceed(s, pkt.AckHandle, data)
} }
} }
@@ -119,25 +194,39 @@ func loadQuestFile(s *Session, questId int) []byte {
} }
decrypted := decryption.UnpackSimple(file) decrypted := decryption.UnpackSimple(file)
if _config.ErupeConfig.RealClientMode <= _config.Z1 && s.server.erupeConfig.DebugOptions.AutoQuestBackport {
decrypted = BackportQuest(decrypted)
}
fileBytes := byteframe.NewByteFrameFromBytes(decrypted) fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
fileBytes.SetLE() fileBytes.SetLE()
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) 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. bodyLength := 320
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(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() questBody.SetLE()
// Find the master quest string pointer // Find the master quest string pointer
questBody.Seek(40, 0) questBody.Seek(40, 0)
fileBytes.Seek(int64(questBody.ReadUint32()), 0) fileBytes.Seek(int64(questBody.ReadUint32()), 0)
questBody.Seek(40, 0) questBody.Seek(40, 0)
// Overwrite it // Overwrite it
questBody.WriteUint32(320) questBody.WriteUint32(uint32(bodyLength))
questBody.Seek(0, 2) questBody.Seek(0, 2)
// Rewrite the quest strings and their pointers // Rewrite the quest strings and their pointers
var tempString []byte var tempString []byte
newStrings := byteframe.NewByteFrame() newStrings := byteframe.NewByteFrame()
tempPointer := 352 tempPointer := bodyLength + 32
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
questBody.WriteUint32(uint32(tempPointer)) questBody.WriteUint32(uint32(tempPointer))
temp := int64(fileBytes.Index()) temp := int64(fileBytes.Index())
@@ -163,7 +252,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
data := loadQuestFile(s, questId) data := loadQuestFile(s, questId)
if data == nil { 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() 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) err = rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDays, &inactiveDays)
if err != nil { if err != nil {
s.logger.Error("Failed to scan event quest row", zap.Error(err))
continue continue
} }
@@ -277,15 +367,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
// Check if the quest is currently active // Check if the quest is currently active
if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) { if currentTime.Before(startTime) || currentTime.After(startTime.Add(time.Duration(activeDays)*24*time.Hour)) {
break continue
} }
} }
data, err := makeEventQuest(s, rows) data, err := makeEventQuest(s, rows)
if err != nil { if err != nil {
s.logger.Error("Failed to make event quest", zap.Error(err))
continue continue
} else { } else {
if len(data) > 896 || len(data) < 352 { if len(data) > 896 || len(data) < 352 {
s.logger.Error("Invalid quest data length", zap.Int("len", len(data)))
continue continue
} else { } else {
totalCount++ totalCount++
@@ -302,11 +394,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tx.Commit() tx.Commit()
} }
type tuneValue struct {
ID uint16
Value uint16
}
tuneValues := []tuneValue{ tuneValues := []tuneValue{
{ID: 20, Value: 1}, {ID: 20, Value: 1},
{ID: 26, Value: 1}, {ID: 26, Value: 1},
@@ -319,43 +406,46 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 67, Value: 1}, {ID: 67, Value: 1},
{ID: 80, Value: 1}, {ID: 80, Value: 1},
{ID: 94, Value: 1}, {ID: 94, Value: 1},
{ID: 1010, Value: 300}, {ID: 1001, Value: 100}, // get_hrp_rate
{ID: 1011, Value: 300}, {ID: 1010, Value: 300}, // get_hrp_rate_netcafe
{ID: 1012, Value: 300}, {ID: 1011, Value: 300}, // get_zeny_rate_netcafe
{ID: 1013, Value: 300}, {ID: 1012, Value: 300}, // get_hrp_rate_ncource
{ID: 1014, Value: 200}, {ID: 1013, Value: 300}, // get_zeny_rate_ncource
{ID: 1015, Value: 200}, {ID: 1014, Value: 200}, // get_hrp_rate_premium
{ID: 1021, Value: 400}, {ID: 1015, Value: 200}, // get_zeny_rate_premium
{ID: 1023, Value: 8}, {ID: 1021, Value: 400}, // get_gcp_rate_assist
{ID: 1024, Value: 150}, {ID: 1023, Value: 8}, // unused?
{ID: 1025, Value: 1}, {ID: 1024, Value: 150}, // get_hrp_rate_ptbonus
{ID: 1025, Value: 1}, // isValid_stampcard
{ID: 1026, Value: 999}, // get_grank_cap {ID: 1026, Value: 999}, // get_grank_cap
{ID: 1027, Value: 100}, {ID: 1027, Value: 100}, // get_exchange_rate_festa
{ID: 1028, Value: 100}, {ID: 1028, Value: 100}, // get_exchange_rate_cafe
{ID: 1030, Value: 8}, {ID: 1030, Value: 8}, // get_gquest_cap
{ID: 1031, Value: 100}, {ID: 1031, Value: 100}, // get_exchange_rate_guild (GCP)
{ID: 1032, Value: 0}, // isValid_partner {ID: 1032, Value: 0}, // isValid_partner
{ID: 1044, Value: 200}, // get_rate_tload_time_out {ID: 1044, Value: 200}, // get_rate_tload_time_out
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset {ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
{ID: 1046, Value: 99}, {ID: 1046, Value: 99}, // get_hunter_life_cap
{ID: 1048, Value: 0}, // get_rate_tower_log_disable {ID: 1048, Value: 0}, // get_rate_tower_hint_sec
{ID: 1049, Value: 10}, // get_rate_tower_gem_max {ID: 1049, Value: 10}, // get_rate_tower_gem_max
{ID: 1050, Value: 1}, // get_rate_tower_gem_set {ID: 1050, Value: 1}, // get_rate_tower_gem_set
{ID: 1051, Value: 200}, {ID: 1051, Value: 200}, // get_pallone_score_rate_premium
{ID: 1052, Value: 200}, {ID: 1052, Value: 200}, // get_trp_rate_premium
{ID: 1063, Value: 50000}, {ID: 1063, Value: 50000}, // get_nboost_quest_point_from_hrank
{ID: 1064, Value: 50000}, {ID: 1064, Value: 50000}, // get_nboost_quest_point_from_srank
{ID: 1065, Value: 25000}, {ID: 1065, Value: 25000}, // get_nboost_quest_point_from_grank
{ID: 1066, Value: 25000}, {ID: 1066, Value: 25000}, // get_nboost_quest_point_from_gsrank
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1? {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: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3? {ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama {ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama {ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama {ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
{ID: 1078, Value: 0}, {ID: 1078, Value: 0}, // isCapped_tenrou_irai
{ID: 1079, Value: 1}, {ID: 1079, Value: 1}, // get_add_tower_level_assist
{ID: 1080, Value: 1}, {ID: 1080, Value: 1}, // get_tune_add_tower_level_w_assist_nboost
// get_tune_secret_book_item
{ID: 1081, Value: 1}, {ID: 1081, Value: 1},
{ID: 1082, Value: 4}, {ID: 1082, Value: 4},
{ID: 1083, Value: 2}, {ID: 1083, Value: 2},
@@ -380,14 +470,17 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 1102, Value: 5}, {ID: 1102, Value: 5},
{ID: 1103, Value: 2}, {ID: 1103, Value: 2},
{ID: 1104, Value: 10}, {ID: 1104, Value: 10},
{ID: 1145, Value: 200},
{ID: 1145, Value: 200}, // get_ud_point_rate_premium
{ID: 1146, Value: 0}, // isTower_invisible {ID: 1146, Value: 0}, // isTower_invisible
{ID: 1147, Value: 0}, // isVenom_playable {ID: 1147, Value: 0}, // isVenom_playable
{ID: 1149, Value: 20}, {ID: 1149, Value: 20}, // get_ud_break_parts_point
{ID: 1152, Value: 1130}, {ID: 1152, Value: 1130}, // unused?
{ID: 1154, Value: 0}, // isDisabled_object_season {ID: 1154, Value: 0}, // isDisabled_object_season
{ID: 1158, Value: 1}, {ID: 1158, Value: 1}, // isDelivery_venom_ult_quest
{ID: 1160, Value: 300}, {ID: 1160, Value: 300}, // get_rate_premium_ravi_g_enhance_tama
// unknown
{ID: 1162, Value: 1}, {ID: 1162, Value: 1},
{ID: 1163, Value: 3}, {ID: 1163, Value: 3},
{ID: 1164, Value: 5}, {ID: 1164, Value: 5},
@@ -407,240 +500,6 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
{ID: 1178, Value: 10}, {ID: 1178, Value: 10},
{ID: 1179, Value: 2}, {ID: 1179, Value: 2},
{ID: 1180, Value: 5}, {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)}) 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 { if s.server.erupeConfig.GameplayOptions.EnableKaijiEvent {
tuneValues = append(tuneValues, tuneValue{1106, 1}) tuneValues = append(tuneValues, tuneValue{1106, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1106, 0})
} }
if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent { if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent {
tuneValues = append(tuneValues, tuneValue{1144, 1}) tuneValues = append(tuneValues, tuneValue{1144, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1144, 0})
} }
if s.server.erupeConfig.GameplayOptions.EnableNierEvent { if s.server.erupeConfig.GameplayOptions.EnableNierEvent {
tuneValues = append(tuneValues, tuneValue{1153, 1}) tuneValues = append(tuneValues, tuneValue{1153, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1153, 0})
} }
if s.server.erupeConfig.GameplayOptions.DisableRoad { if s.server.erupeConfig.GameplayOptions.DisableRoad {
tuneValues = append(tuneValues, tuneValue{1155, 1}) tuneValues = append(tuneValues, tuneValue{1155, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1155, 0})
} }
for i := uint16(0); i < 13; i++ { // get_hrp_rate_from_rank
tuneValues = append(tuneValues, tuneValue{i + 3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier * 100)}) 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++ { var temp []tuneValue
tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)}) 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 = temp
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)
tuneLimit := 770 tuneLimit := 770
if _config.ErupeConfig.RealClientMode <= _config.F5 { if _config.ErupeConfig.RealClientMode <= _config.F5 {
@@ -737,6 +601,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tuneValues = tuneValues[:tuneLimit] tuneValues = tuneValues[:tuneLimit]
} }
offset := uint16(time.Now().Unix())
bf.WriteUint16(offset)
bf.WriteUint16(uint16(len(tuneValues))) bf.WriteUint16(uint16(len(tuneValues)))
for i := range tuneValues { for i := range tuneValues {
bf.WriteUint16(tuneValues[i].ID ^ offset) bf.WriteUint16(tuneValues[i].ID ^ offset)
@@ -778,6 +645,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) 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 handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -157,7 +157,6 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
s.stage.reservedClientSlots[s.charID] = false s.stage.reservedClientSlots[s.charID] = false
s.stage.Unlock() s.stage.Unlock()
s.stageMoveStack.Push(s.stage.id) s.stageMoveStack.Push(s.stage.id)
s.stageMoveStack.Lock()
} }
if s.reservationStage != nil { if s.reservationStage != nil {
@@ -171,7 +170,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysBackStage) pkt := p.(*mhfpacket.MsgSysBackStage)
// Transfer back to the saved stage ID before the previous move or enter. // Transfer back to the saved stage ID before the previous move or enter.
s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop() backStage, err := s.stageMoveStack.Pop()
if backStage == "" || err != nil { if backStage == "" || err != nil {
backStage = "sl1Ns200p0a0u0" backStage = "sl1Ns200p0a0u0"
@@ -190,12 +188,6 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysMoveStage) 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) doStageTransfer(s, pkt.AckHandle, pkt.StageID)
} }

View File

@@ -1,8 +1,10 @@
package channelserver package channelserver
import ( import (
_config "erupe-ce/config"
"fmt" "fmt"
"go.uber.org/zap" "go.uber.org/zap"
"strings"
"time" "time"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
@@ -17,7 +19,7 @@ type TowerInfoTRP struct {
type TowerInfoSkill struct { type TowerInfoSkill struct {
TSP int32 TSP int32
Unk1 []int16 // 40 Skills []int16 // 64
} }
type TowerInfoHistory struct { type TowerInfoHistory struct {
@@ -32,6 +34,14 @@ type TowerInfoLevel struct {
Unk3 int32 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) { func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo) pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
var data []*byteframe.ByteFrame var data []*byteframe.ByteFrame
@@ -44,21 +54,24 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
towerInfo := TowerInfo{ towerInfo := TowerInfo{
TRP: []TowerInfoTRP{{0, 0}}, TRP: []TowerInfoTRP{{0, 0}},
Skill: []TowerInfoSkill{{0, make([]int16, 40)}}, Skill: []TowerInfoSkill{{0, make([]int16, 64)}},
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}}, History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}}, 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" 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
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 `, 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)
`, 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 { if err != nil {
s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID) 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) { for i, skill := range stringsupport.CSVElems(tempSkills) {
towerInfo.Skill[0].Unk1[i] = int16(skill) towerInfo.Skill[0].Skills[i] = int16(skill)
} }
switch pkt.InfoType { switch pkt.InfoType {
@@ -73,8 +86,8 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
for _, skills := range towerInfo.Skill { for _, skills := range towerInfo.Skill {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteInt32(skills.TSP) bf.WriteInt32(skills.TSP)
for i := range skills.Unk1 { for i := range skills.Skills {
bf.WriteInt16(skills.Unk1[i]) bf.WriteInt16(skills.Skills[i])
} }
data = append(data, bf) data = append(data, bf)
} }
@@ -89,7 +102,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
} }
data = append(data, bf) data = append(data, bf)
} }
case 5: case 3, 5:
for _, level := range towerInfo.Level { for _, level := range towerInfo.Level {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteInt32(level.Floors) bf.WriteInt32(level.Floors)
@@ -105,7 +118,7 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo) pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools { if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug( s.logger.Debug(
p.Opcode().String(), p.Opcode().String(),
zap.Uint32("InfoType", pkt.InfoType), zap.Uint32("InfoType", pkt.InfoType),
@@ -123,8 +136,8 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
switch pkt.InfoType { switch pkt.InfoType {
case 2: 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" var skills string
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills) 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) 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: case 1, 7:
// This might give too much TSP? No idea what the rate is supposed to be // 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) { func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai) pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools { if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug( s.logger.Debug(
p.Opcode().String(), p.Opcode().String(),
zap.Uint8("Unk0", pkt.Unk0), zap.Uint8("Unk0", pkt.Unk0),
@@ -412,10 +425,10 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
gemInfo := []GemInfo{} gemInfo := []GemInfo{}
gemHistory := []GemHistory{} 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" var tempGems string
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems) 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) { 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 { switch pkt.Unk0 {
@@ -442,7 +455,7 @@ func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostGemInfo) pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools { if s.server.erupeConfig.DebugOptions.QuestTools {
s.logger.Debug( s.logger.Debug(
p.Opcode().String(), p.Opcode().String(),
zap.Uint32("Op", pkt.Op), 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" var gems string
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems) s.server.db.QueryRow(`SELECT COALESCE(gems, $1) FROM tower WHERE char_id=$2`, EmptyTowerCSV(30), s.charID).Scan(&gems)
switch pkt.Op { switch pkt.Op {
case 1: // Add gem 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) 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 case 2: // Transfer gem
// no way im doing this for now // no way im doing this for now

View File

@@ -57,7 +57,7 @@ type Server struct {
stages map[string]*Stage stages map[string]*Stage
// Used to map different languages // Used to map different languages
dict map[string]string i18n i18n
// UserBinary // UserBinary
userBinaryPartsLock sync.RWMutex userBinaryPartsLock sync.RWMutex
@@ -192,7 +192,7 @@ func NewServer(config *Config) *Server {
// MezFes // MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0") s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
s.dict = getLangStrings(s) s.i18n = getLangStrings(s)
return s return s
} }
@@ -211,6 +211,7 @@ func (s *Server) Start() error {
// Start the discord bot for chat integration. // Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil { if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
s.discordBot.Session.AddHandler(s.onDiscordMessage) s.discordBot.Session.AddHandler(s.onDiscordMessage)
s.discordBot.Session.AddHandler(s.onInteraction)
} }
return nil return nil
@@ -336,13 +337,13 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
var text string var text string
switch _type { switch _type {
case 2: case 2:
text = s.dict["ravienteBerserk"] text = s.i18n.raviente.berserk
case 3: case 3:
text = s.dict["ravienteExtreme"] text = s.i18n.raviente.extreme
case 4: case 4:
text = s.dict["ravienteExtremeLimited"] text = s.i18n.raviente.extremeLimited
case 5: case 5:
text = s.dict["ravienteBerserkSmall"] text = s.i18n.raviente.berserkSmall
default: default:
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type)) s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
} }
@@ -377,6 +378,26 @@ func (s *Server) FindSessionByCharID(charID uint32) *Session {
return nil 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 { func (s *Server) FindObjectByChar(charID uint32) *Object {
s.stagesLock.RLock() s.stagesLock.RLock()
defer s.stagesLock.RUnlock() defer s.stagesLock.RUnlock()

View File

@@ -1,114 +1,238 @@
package channelserver package channelserver
func getLangStrings(s *Server) map[string]string { type i18n struct {
strings := make(map[string]string) 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 { switch s.erupeConfig.Language {
case "jp": case "jp":
strings["language"] = "日本語" i.language = "日本語"
strings["cafeReset"] = "%d/%dにリセット" i.cafe.reset = "%d/%dにリセット"
i.timer = "タイマー:%02d'%02d\"%02d.%03d (%df)"
strings["commandDisabled"] = "%sのコマンドは無効です" i.commands.noOp = "You don't have permission to use this command"
strings["commandReload"] = "リロードします" i.commands.disabled = "%sのコマンドは無効です"
strings["commandKqfGet"] = "現在のキークエストフラグ:%x" i.commands.reload = "リロードします"
strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx" i.commands.kqf.get = "現在のキークエストフラグ:%x"
strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください" i.commands.kqf.set.error = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
strings["commandKqfVersion"] = "This command is disabled prior to MHFG10" i.commands.kqf.set.success = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x" i.commands.kqf.version = "This command is disabled prior to MHFG10"
strings["commandRightsSuccess"] = "コース情報を更新しました:%d" i.commands.rights.error = "コース更新コマンドエラー 例:%s x"
strings["commandCourseError"] = "コース確認コマンドエラー 例:%s <name>" i.commands.rights.success = "コース情報を更新しました:%d"
strings["commandCourseDisabled"] = "%sコースは無効です" i.commands.course.error = "コース確認コマンドエラー 例:%s <name>"
strings["commandCourseEnabled"] = "%sコースは効です" i.commands.course.disabled = "%sコースは効です"
strings["commandCourseLocked"] = "%sコースはロックされています" i.commands.course.enabled = "%sコースは有効です"
strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y" i.commands.course.locked = "%sコースはロックされています"
strings["commandTeleportSuccess"] = "%d %dにテレポート" i.commands.teleport.error = "テレポートコマンドエラー 構文:%s x y"
strings["commandPSNError"] = "PSN連携コマンドエラー %s <psn id>" i.commands.teleport.success = "%d %dにテレポート"
strings["commandPSNSuccess"] = "PSN「%s」が連携されています" i.commands.psn.error = "PSN連携コマンドエラー %s <psn id>"
strings["commandPSNExists"] = "PSNは既存のユーザに接続されています" i.commands.psn.success = "PSN「%s」が連携されています"
i.commands.psn.exists = "PSNは既存のユーザに接続されています"
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません" i.commands.discord.success = "あなたのDiscordトークン%s"
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"
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!" i.commands.ban.noUser = "Could not find user"
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!" i.commands.ban.success = "Successfully banned %s"
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!" i.commands.ban.invalid = "Invalid Character ID"
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!" i.commands.ban.error = "Error in command. Format: %s <id> [length]"
i.commands.ban.length = " until %s"
strings["guildInviteName"] = "猟団勧誘のご案内" i.commands.ravi.noCommand = "ラヴィコマンドが指定されていません"
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。" 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"] = "成功" i.raviente.berserk = "<大討伐:猛狂期>が開催されました!"
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました" i.raviente.extreme = "<大討伐:猛狂期【極】>が開催されました"
i.raviente.extremeLimited = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
i.raviente.berserkSmall = "<大討伐:猛狂期(小数)>が開催されました!"
strings["guildInviteAcceptedName"] = "承諾されました" i.guild.invite.title = "猟団勧誘のご案内"
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。" i.guild.invite.body = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
strings["guildInviteRejectName"] = "却下しました" i.guild.invite.success.title = "成功"
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。" i.guild.invite.success.body = "あなたは「%s」に参加できました。"
strings["guildInviteDeclinedName"] = "辞退しました" i.guild.invite.accepted.title = "承諾されました"
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。" 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: default:
strings["language"] = "English" i.language = "English"
strings["cafeReset"] = "Resets on %d/%d" i.cafe.reset = "Resets on %d/%d"
i.timer = "Time: %02d:%02d:%02d.%03d (%df)"
strings["commandDisabled"] = "%s command is disabled" i.commands.noOp = "You don't have permission to use this command"
strings["commandReload"] = "Reloading players..." i.commands.disabled = "%s command is disabled"
strings["commandKqfGet"] = "KQF: %x" i.commands.reload = "Reloading players..."
strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx" i.commands.kqf.get = "KQF: %x"
strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World" i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
strings["commandKqfVersion"] = "This command is disabled prior to MHFG10" i.commands.kqf.set.success = "KQF set, please switch Land/World"
strings["commandRightsError"] = "Error in command. Format: %s x" i.commands.kqf.version = "This command is disabled prior to MHFG10"
strings["commandRightsSuccess"] = "Set rights integer: %d" i.commands.rights.error = "Error in command. Format: %s x"
strings["commandCourseError"] = "Error in command. Format: %s <name>" i.commands.rights.success = "Set rights integer: %d"
strings["commandCourseDisabled"] = "%s Course disabled" i.commands.course.error = "Error in command. Format: %s <name>"
strings["commandCourseEnabled"] = "%s Course enabled" i.commands.course.disabled = "%s Course disabled"
strings["commandCourseLocked"] = "%s Course is locked" i.commands.course.enabled = "%s Course enabled"
strings["commandTeleportError"] = "Error in command. Format: %s x y" i.commands.course.locked = "%s Course is locked"
strings["commandTeleportSuccess"] = "Teleporting to %d %d" i.commands.teleport.error = "Error in command. Format: %s x y"
strings["commandPSNError"] = "Error in command. Format: %s <psn id>" i.commands.teleport.success = "Teleporting to %d %d"
strings["commandPSNSuccess"] = "Connected PSN ID: %s" i.commands.psn.error = "Error in command. Format: %s <psn id>"
strings["commandPSNExists"] = "PSN ID is connected to another account!" 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!" i.commands.discord.success = "Your Discord token: %s"
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"
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!" i.commands.ban.noUser = "Could not find user"
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!" i.commands.ban.success = "Successfully banned %s"
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!" i.commands.ban.invalid = "Invalid Character ID"
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!" i.commands.ban.error = "Error in command. Format: %s <id> [length]"
i.commands.ban.length = " until %s"
strings["guildInviteName"] = "Invitation!" i.commands.timer.enabled = "Quest timer enabled"
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?" i.commands.timer.disabled = "Quest timer disabled"
strings["guildInviteSuccessName"] = "Success!" i.commands.ravi.noCommand = "No Raviente command specified!"
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」." 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" i.raviente.berserk = "<Great Slaying: Berserk> is being held!"
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」." 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" i.guild.invite.title = "Invitation!"
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」." i.guild.invite.body = "You have been invited to join\n「%s」\nDo you want to accept?"
strings["guildInviteDeclinedName"] = "Declined" i.guild.invite.success.title = "Success!"
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」." 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
} }

View File

@@ -86,13 +86,11 @@ func NewSession(server *Server, conn net.Conn) *Session {
// Start starts the session packet send and recv loop(s). // Start starts the session packet send and recv loop(s).
func (s *Session) Start() { func (s *Session) Start() {
go func() {
s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String())) s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String()))
// Unlike the sign and entrance server, // Unlike the sign and entrance server,
// the client DOES NOT initalize the channel connection with 8 NULL bytes. // the client DOES NOT initalize the channel connection with 8 NULL bytes.
go s.sendLoop() go s.sendLoop()
s.recvLoop() go s.recvLoop()
}()
} }
// QueueSend queues a packet (raw []byte) to be sent. // 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() { func (s *Session) sendLoop() {
var pkt packet
for { for {
if s.closed { if s.closed {
return return
} }
pkt := <-s.sendPackets for len(s.sendPackets) > 0 {
pkt = <-s.sendPackets
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...)) err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
if err != nil { if err != nil {
s.logger.Warn("Failed to send packet") s.logger.Warn("Failed to send packet")
} }
time.Sleep(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)) s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
logoutPlayer(s) logoutPlayer(s)
return return
} } else if err != nil {
if err != nil {
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err)) s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
logoutPlayer(s) logoutPlayer(s)
return return
} }
s.handlePacketGroup(pkt) 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) { 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 return
} } else if sender != "Server" && !s.server.erupeConfig.DebugOptions.LogInboundMessages {
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
return
} else if sender != "Server" && !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
return 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("[%s] -> [%s]\n", sender, recipient)
} }
fmt.Printf("Opcode: %s\n", opcodePID) fmt.Printf("Opcode: %s\n", opcodePID)
if s.server.erupeConfig.DevModeOptions.LogMessageData { if s.server.erupeConfig.DebugOptions.LogMessageData {
if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength { if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data)) fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
} else { } else {
fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data)) fmt.Printf("Data [%d bytes]: (Too long!)\n\n", len(data))
@@ -313,3 +309,12 @@ func (s *Session) NextObjectID() uint32 {
bf.Seek(0, 0) bf.Seek(0, 0)
return bf.ReadUint32() 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
}

View File

@@ -7,12 +7,39 @@ import (
"regexp" "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 { type DiscordBot struct {
Session *discordgo.Session Session *discordgo.Session
config *_config.Config config *_config.Config
logger *zap.Logger logger *zap.Logger
MainGuild *discordgo.Guild MainGuild *discordgo.Guild
RealtimeChannel *discordgo.Channel RelayChannel *discordgo.Channel
} }
type Options struct { type Options struct {
@@ -28,10 +55,14 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
return nil, err 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 { 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 return nil, err
} }
@@ -39,7 +70,7 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
config: options.Config, config: options.Config,
logger: options.Logger, logger: options.Logger,
Session: session, Session: session,
RealtimeChannel: realtimeChannel, RelayChannel: relayChannel,
} }
return return
@@ -51,7 +82,7 @@ func (bot *DiscordBot) Start() (err error) {
return 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 { func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`) userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`) 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) { 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 return
} }

View File

@@ -12,45 +12,40 @@ var (
// CalcSum32 calculates the custom MHF "sum32" checksum of the given data. // CalcSum32 calculates the custom MHF "sum32" checksum of the given data.
func CalcSum32(data []byte) uint32 { func CalcSum32(data []byte) uint32 {
tableIdx0 := int(len(data) & 0xFF) tableIdx0 := (len(data) + 1) & 0xFF
tableIdx1 := int(data[len(data)>>1] & 0xFF) tableIdx1 := int((data[len(data)>>1] + 1) & 0xFF)
out := make([]byte, 4) out := make([]byte, 4)
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
tableIdx0++ key := data[i] ^ _sum32Table0[(tableIdx0+i)%7] ^ _sum32Table1[(tableIdx1+i)%9]
tableIdx1++ out[i&3] = (out[i&3] + key) & 0xFF
}
tmp := byte((_sum32Table1[tableIdx1%9] ^ _sum32Table0[tableIdx0%7]) ^ data[i]) return binary.BigEndian.Uint32(out)
out[i&3] = (out[i&3] + tmp) & 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. // EncryptBin8 encrypts the given data using MHF's "binary8" encryption.
func EncryptBin8(data []byte, key byte) []byte { func EncryptBin8(data []byte, key byte) []byte {
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF) _key := uint32(key)
var output []byte var output []byte
for i := 0; i < len(data); i++ { 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) output = append(output, data[i]^tmp)
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
} }
return output return output
} }
// DecryptBin8 decrypts the given MHF "binary8" data. // DecryptBin8 decrypts the given MHF "binary8" data.
func DecryptBin8(data []byte, key byte) []byte { func DecryptBin8(data []byte, key byte) []byte {
curKey := uint32(((54323 * uint(key)) + 1) & 0xFFFFFFFF) _key := uint32(key)
var output []byte var output []byte
for i := 0; i < len(data); i++ { 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]) output = append(output, tmp^_bin8Key[i&7])
curKey = uint32(((54323 * uint(curKey)) + 1) & 0xFFFFFFFF)
} }
return output return output
} }

View File

@@ -111,7 +111,7 @@ func (s *Server) handleEntranceServerConnection(conn net.Conn) {
return 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)) fmt.Printf("[Client] -> [Server]\nData [%d bytes]:\n%s\n", len(pkt), hex.Dump(pkt))
} }

View File

@@ -29,7 +29,6 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
} }
} }
sid := (4096 + serverIdx*256) * 6000
if si.IP == "" { if si.IP == "" {
si.IP = config.Host si.IP = config.Host
} }
@@ -38,8 +37,8 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
} else { } else {
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4())) bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
} }
bf.WriteUint16(16 + uint16(serverIdx)) bf.WriteUint16(uint16(serverIdx | 16))
bf.WriteUint16(0x0000) bf.WriteUint16(0)
bf.WriteUint16(uint16(len(si.Channels))) bf.WriteUint16(uint16(len(si.Channels)))
bf.WriteUint8(si.Type) bf.WriteUint8(si.Type)
bf.WriteUint8(uint8(((channelserver.TimeAdjusted().Unix() / 86400) + int64(serverIdx)) % 3)) 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) bf.WriteUint8(si.Recommended)
} }
if s.erupeConfig.RealClientMode <= _config.F5 { fullName := append(append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...), stringsupport.UTF8ToSJIS(si.Description)...)
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) if s.erupeConfig.RealClientMode >= _config.G1 && s.erupeConfig.RealClientMode <= _config.G5 {
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) bf.WriteUint8(uint8(len(fullName)))
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false)) bf.WriteBytes(fullName)
} 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)
} else { } else {
bf.WriteUint8(0) // Prevents malformed server name if s.erupeConfig.RealClientMode >= _config.G51 {
combined := append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...) bf.WriteUint8(0) // Ignored
combined = append(combined, stringsupport.UTF8ToSJIS(si.Description)...) }
bf.WriteBytes(stringsupport.PaddedString(string(combined), 65, false)) bf.WriteBytes(stringsupport.PaddedString(string(fullName), 65, false))
} }
if s.erupeConfig.RealClientMode >= _config.GG { 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 { for channelIdx, ci := range si.Channels {
sid = (4096 + serverIdx*256) + (16 + channelIdx) sid := (serverIdx<<8 | 4096) + (channelIdx | 16)
if _config.ErupeConfig.DevMode && _config.ErupeConfig.ProxyPort != 0 { if _config.ErupeConfig.DebugOptions.ProxyPort != 0 {
bf.WriteUint16(_config.ErupeConfig.ProxyPort) bf.WriteUint16(_config.ErupeConfig.DebugOptions.ProxyPort)
} else { } else {
bf.WriteUint16(ci.Port) bf.WriteUint16(ci.Port)
} }
bf.WriteUint16(16 + uint16(channelIdx)) bf.WriteUint16(uint16(channelIdx | 16))
bf.WriteUint16(ci.MaxPlayers) bf.WriteUint16(ci.MaxPlayers)
var currentPlayers uint16 var currentPlayers uint16
s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(&currentPlayers) s.db.QueryRow("SELECT current_players FROM servers WHERE server_id=$1", sid).Scan(&currentPlayers)
bf.WriteUint16(currentPlayers) bf.WriteUint16(currentPlayers)
bf.WriteUint16(0) // Unk bf.WriteUint16(0)
bf.WriteUint16(0) // Unk bf.WriteUint16(0)
bf.WriteUint16(0) // Unk bf.WriteUint16(0)
bf.WriteUint16(0) // Unk bf.WriteUint16(0)
bf.WriteUint16(0) // Unk bf.WriteUint16(0)
bf.WriteUint16(0) // Unk bf.WriteUint16(0)
bf.WriteUint16(319) // Unk bf.WriteUint16(319) // Unk
bf.WriteUint16(252) // Unk bf.WriteUint16(254 - currentPlayers) // Unk
bf.WriteUint16(248) // Unk bf.WriteUint16(255 - currentPlayers) // Unk
bf.WriteUint16(12345) // Unk bf.WriteUint16(12345)
} }
} }
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix())) bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
bf.WriteUint32(0x0000003C) bf.WriteUint32(60)
return bf.Data() return bf.Data()
} }
@@ -136,7 +130,7 @@ func makeSv2Resp(config *_config.Config, s *Server, local bool) []byte {
} }
rawServerData := encodeServerInfo(config, s, local) 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)) 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 var sid uint16
err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid) err := s.db.QueryRow("SELECT(SELECT server_id FROM sign_sessions WHERE char_id=$1) AS _", cid).Scan(&sid)
if err != nil { if err != nil {
resp.WriteBytes(make([]byte, 4)) resp.WriteUint16(0)
} else { } else {
resp.WriteUint16(sid) 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())) fmt.Printf("[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(resp.Data()), hex.Dump(resp.Data()))
} }

View File

@@ -198,17 +198,17 @@ func (s *Server) checkToken(uid uint32) (bool, error) {
} }
func (s *Server) registerUidToken(uid uint32) (uint32, string, error) { func (s *Server) registerUidToken(uid uint32) (uint32, string, error) {
token := token.Generate(16) _token := token.Generate(16)
var tid uint32 var tid uint32
err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, token).Scan(&tid) err := s.db.QueryRow(`INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id`, uid, _token).Scan(&tid)
return tid, token, err return tid, _token, err
} }
func (s *Server) registerPsnToken(psn string) (uint32, string, error) { func (s *Server) registerPsnToken(psn string) (uint32, string, error) {
token := token.Generate(16) _token := token.Generate(16)
var tid uint32 var tid uint32
err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, token).Scan(&tid) err := s.db.QueryRow(`INSERT INTO sign_sessions (psn_id, token) VALUES ($1, $2) RETURNING id`, psn, _token).Scan(&tid)
return tid, token, err return tid, _token, err
} }
func (s *Server) validateToken(token string, tokenID uint32) bool { 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 var passDB string
err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB) err := s.db.QueryRow(`SELECT id, password FROM users WHERE username = $1`, user).Scan(&uid, &passDB)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if errors.Is(err, sql.ErrNoRows) {
s.logger.Info("User not found", zap.String("User", user)) 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) uid, err = s.registerDBAccount(user, pass)
if err == nil { if err == nil {
return uid, SIGN_SUCCESS return uid, SIGN_SUCCESS
@@ -244,6 +244,15 @@ func (s *Server) validateLogin(user string, pass string) (uint32, RespID) {
return 0, SIGN_EABORT return 0, SIGN_EABORT
} else { } else {
if bcrypt.CompareHashAndPassword([]byte(passDB), []byte(pass)) == nil { 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 uid, SIGN_SUCCESS
} }
return 0, SIGN_EPASS return 0, SIGN_EPASS

View File

@@ -72,7 +72,7 @@ func (s *Session) makeSignResponse(uid uint32) []byte {
bf.WriteUint32(char.ID) bf.WriteUint32(char.ID)
// Exp, HR[x] is split by 0, 1, 30, 50, 99, 299, 998, 999 // 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) bf.WriteUint16(999)
} else { } else {
bf.WriteUint16(char.HRP) 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) s.server.db.QueryRow("SELECT psn_id FROM users WHERE id = $1", uid).Scan(&psnUser)
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true)) bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
} }
bf.WriteUint16(0xCA10)
bf.WriteUint16(0x4E20) bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[0])
ps.Uint16(bf, "", false) // unk key if s.server.erupeConfig.DebugOptions.CapLink.Values[0] == 51728 {
bf.WriteUint8(0x00) bf.WriteUint16(s.server.erupeConfig.DebugOptions.CapLink.Values[1])
bf.WriteUint16(0xCA11) if s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20000 || s.server.erupeConfig.DebugOptions.CapLink.Values[1] == 20002 {
bf.WriteUint16(0x0001) ps.Uint16(bf, s.server.erupeConfig.DebugOptions.CapLink.Key, false)
bf.WriteUint16(0x4E20) }
ps.Uint16(bf, "", false) // unk ipv4 }
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(uint32(s.server.getReturnExpiry(uid).Unix()))
bf.WriteUint32(0) bf.WriteUint32(0)
tickets := []uint32{ tickets := []uint32{
s.server.erupeConfig.GameplayOptions.MezfesSoloTickets, s.server.erupeConfig.GameplayOptions.MezFesSoloTickets,
s.server.erupeConfig.GameplayOptions.MezfesGroupTickets, s.server.erupeConfig.GameplayOptions.MezFesGroupTickets,
} }
stalls := []uint8{ stalls := []uint8{
10, 3, 6, 9, 4, 8, 5, 7, 10, 3, 6, 9, 4, 8, 5, 7,

View File

@@ -37,7 +37,7 @@ type Session struct {
func (s *Session) work() { func (s *Session) work() {
pkt, err := s.cryptConn.ReadPacket() 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)) 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) bf := byteframe.NewByteFrameFromBytes(pkt)
reqType := string(bf.ReadNullTerminatedBytes()) reqType := string(bf.ReadNullTerminatedBytes())
switch reqType[:len(reqType)-3] { switch reqType[:len(reqType)-3] {
case "DLTSKEYSIGN:", "DSGN:": case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
s.handleDSGN(bf) s.handleDSGN(bf)
case "PS3SGN:": case "PS3SGN:":
s.client = PS3 s.client = PS3
@@ -78,7 +78,7 @@ func (s *Session) handlePacket(pkt []byte) error {
} }
default: default:
s.logger.Warn("Unknown request", zap.String("reqType", reqType)) 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)) 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: default:
bf.WriteUint8(uint8(resp)) 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())) fmt.Printf("\n[Server] -> [Client]\nData [%d bytes]:\n%s\n", len(bf.Data()), hex.Dump(bf.Data()))
} }
_ = s.cryptConn.SendPacket(bf.Data()) _ = s.cryptConn.SendPacket(bf.Data())

View File

@@ -80,7 +80,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
PatchServer: s.erupeConfig.SignV2.PatchServer, PatchServer: s.erupeConfig.SignV2.PatchServer,
Notices: []string{}, Notices: []string{},
} }
if s.erupeConfig.DevModeOptions.MaxLauncherHR { if s.erupeConfig.DebugOptions.MaxLauncherHR {
for i := range resp.Characters { for i := range resp.Characters {
resp.Characters[i].HR = 7 resp.Characters[i].HR = 7
} }
@@ -93,8 +93,8 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
ID: uint32(channelserver.TimeWeekStart().Unix()), ID: uint32(channelserver.TimeWeekStart().Unix()),
Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()), Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()),
End: uint32(channelserver.TimeWeekNext().Unix()), End: uint32(channelserver.TimeWeekNext().Unix()),
SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets,
GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets, GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets,
Stalls: stalls, Stalls: stalls,
} }
if !s.erupeConfig.HideLoginNotice { if !s.erupeConfig.HideLoginNotice {
@@ -226,7 +226,7 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500) w.WriteHeader(500)
return return
} }
if s.erupeConfig.DevModeOptions.MaxLauncherHR { if s.erupeConfig.DebugOptions.MaxLauncherHR {
character.HR = 7 character.HR = 7
} }
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")