156 Commits

Author SHA1 Message Date
stratic-dev
93b107362c Rename of variables and wip info 2024-07-31 14:23:32 +01:00
wish
f70fd5e6e7 basic BeatLevel progress storage 2024-07-31 22:00:44 +10:00
wish
757c98a9a8 various changes 2024-07-14 23:10:32 +10:00
stratic-dev
6cf7c1a4b7 Removed EarthStatus for Event System 2024-07-11 20:48:09 +01:00
stratic-dev
2a1c80b2af Paperdata values discovery 2024-07-05 22:38:35 +01:00
stratic-dev
9840e55056 Clean up present box and added remaining tower items 2024-07-05 19:14:10 +01:00
stratic-dev
bd58dccfb6 Base for present type 2024-07-05 02:39:44 +01:00
stratic-dev
9330555820 Put back config but keep Earth Monsters and CA and branch client version 2024-07-02 00:56:46 +01:00
stratic-dev
c5ccab45cc Accidently removed config 2024-07-02 00:51:22 +01:00
stratic-dev
0d589191db Cleaned up PresentBox and Fixed Op Issue for PD 2024-07-02 00:50:07 +01:00
stratic-dev
5f665e4262 Added some conquest packets and caplink paths 2024-07-01 16:16:57 +01:00
stratic-dev
163444950c figured out seibatu type and found pd for typ 4 2024-07-01 02:13:58 +01:00
wish
0f38c4b85d update comments 2024-06-30 12:45:40 +10:00
stratic-dev
2247d4c527 Added more comments cleaned up data 2024-06-30 02:36:40 +01:00
stratic-dev
b481e73673 Added some pallone support 2024-06-30 00:28:58 +01:00
stratic-dev
40095574d1 Merge branch 'main' into tower-experiments 2024-06-29 23:53:11 +01:00
wish
dd13713bc1 fix parsing SysTerminalLog 2024-06-25 22:50:58 +10:00
wish
12c7774cc1 fix GetCafeDuration 2024-06-25 20:35:56 +10:00
wish
e12f444b8d fix GetCafeDuration 2024-06-16 18:16:12 +10:00
wish
100ec30fba fix GetCafeDuration 2024-06-16 17:46:12 +10:00
stratic-dev
dc59755384 Added changes for Z1 Tower with CapLink
Added TinyBin Item code and some notes, Added CapLink API
2024-04-01 22:08:58 +01:00
stratic-dev
b8be6e7aa8 Merge pull request #120 from Nageld/main
docker updates
2024-03-29 14:14:55 +00:00
stratic-dev
853758a951 Merge pull request #119 from ZeruLight/feature/ps4
Feature/ps4
2024-03-24 19:59:19 +00:00
nageld
5342dc4df1 reference the bin and config in the root directory so they don't need to be duplicated in docker folder 2024-03-23 14:19:23 -04:00
nageld
a10ecf2a11 update init path 2024-03-23 14:17:14 -04:00
stratic-dev
449c3e443b Merge pull request #117 from ZeruLight/feature/screenshot-api
Feature/screenshot api
2024-03-20 19:49:10 +00:00
stratic-dev
4d134d0624 Remove reformatting 2024-03-20 19:44:54 +00:00
stratic-dev
7d7fd50ba8 init ps4 support 2024-03-20 19:12:57 +00:00
wish
fc13b1bdd9 Merge pull request #118 from ZeruLight/fix/weekly-stamps
fix/weekly-stamps
2024-03-18 21:33:41 +11:00
wish
d26ae4563e fix G1 compatibility 2024-03-17 16:35:16 +11:00
wish
76858bb111 bypass full Stage check if reserve slot exists 2024-03-16 21:02:49 +11:00
stratic-dev
295ff6537b Added utils to verify paths 2024-03-15 20:00:39 +00:00
stratic-dev
d123182a2f Renamed signv2 to api and enabled it by default 2024-03-15 19:37:55 +00:00
stratic-dev
62a2fe9f73 Added more regex 2024-03-15 18:43:33 +00:00
stratic-dev
12b3dd1be3 Add regex 2024-03-15 18:33:23 +00:00
stratic-dev
3797438ca2 No database 2024-03-15 00:54:18 +00:00
stratic-dev
def2bc3d2c initial commit 2024-03-12 23:00:01 +00:00
wish
25bb69b6df test weekly stamp fix 2024-03-11 23:31:39 +11:00
wish
19aadc6e10 enforce Stage.maxPlayers on MoveStage & BackStage 2024-03-10 19:50:21 +11:00
wish
5284fe55cd enforce Stage.maxPlayers on EnterStage 2024-03-10 18:33:55 +11:00
wish
b08c41a886 enforce Stage.maxPlayers on EnterStage 2024-03-10 18:26:26 +11:00
wish
bfb22951f2 fix PatchServer response 2024-03-10 11:05:41 +11:00
wish
89c1db4712 remove PS3 Patch Server default 2024-03-09 14:58:12 +11:00
wish
fac68a2b4b fix UpdateGuacot 2024-03-06 22:17:27 +11:00
wish
d9479ea863 move initialization schema 2024-03-02 21:39:23 +11:00
wish
734a982689 proofreading 2024-03-02 21:37:25 +11:00
wish
846f8d6693 retain excess Room RP 2024-02-28 21:24:55 +11:00
wish
e5703617bb add support for Clan Changing Room 2024-02-28 19:29:06 +11:00
wish
76425efd2e squash psn-link patch-schema 2024-02-25 22:26:44 +11:00
wish
f5c772413e rename instances of HRP to HR 2024-02-25 22:20:12 +11:00
wish
a0282bd11e prevent rand.Intn panic 2024-02-25 21:32:07 +11:00
wish
7640195d2b add min and max Feature Weapons 2024-02-25 21:18:14 +11:00
wish
d217ae1a85 Merge pull request #115 from ZeruLight/feature/warehouse-v2
feature/warehouse-v2
2024-02-25 15:38:35 +11:00
wish
ecffe63d6b index schema 2024-02-25 15:37:26 +11:00
wish
c74ce4b07f simplify reused code 2024-02-25 14:22:21 +11:00
wish
40d4aba3c4 convert other Warehouse types to new system 2024-02-25 14:12:37 +11:00
wish
a9b9c94347 fix RNG 2024-02-21 03:46:15 +11:00
wish
b969c53f3a fix Warehouse packet parsing 2024-02-21 02:09:42 +11:00
wish
e80a03dcc7 fix Warehouse Item functions 2024-02-21 02:09:26 +11:00
wish
020f122bb0 use better RNG for Warehouse IDs 2024-02-21 02:08:59 +11:00
wish
caf4deb1a6 fix Warehouse Equipment dereference 2024-02-20 21:16:31 +11:00
wish
9f19358c8b fix Warehouse serialisation across versions 2024-02-20 21:04:29 +11:00
wish
f51d65cf02 Merge branch 'main' into feature/warehouse-v2 2024-02-20 20:46:49 +11:00
wish
d0e727d444 fix remaining Festa queries 2024-02-20 18:52:56 +11:00
wish
db364110eb Merge branch 'main' into feature/warehouse-v2 2024-02-20 17:50:04 +11:00
wish
c3409996ef rollback unknown Festa values 2024-02-20 15:02:10 +11:00
wish
a968f18438 add support for SIGN requests 2024-02-20 14:53:59 +11:00
wish
c5905d74d4 index Patch Schemas 2024-02-20 04:19:43 +11:00
wish
5bcfe25ede implement Festa Bonus Categories & Guild Character optimisations 2024-02-20 04:18:16 +11:00
wish
d22a7c782f changes to FestivalColor 2024-02-20 04:16:26 +11:00
wish
1c4370b929 fix EnumerateFestaMember prior to Z2 2024-02-20 04:12:22 +11:00
wish
df062613eb add Save pointers for S6.0 2024-02-20 03:08:48 +11:00
wish
b73f85ef4a add Quest timer toggle Chat Command 2024-02-20 00:33:38 +11:00
wish
9cfbd92454 add blank Winner entries to InfoFesta 2024-02-19 18:36:03 +11:00
wish
183f88654b fix InfoFesta response on S6.0 2024-02-19 18:12:51 +11:00
wish
864586a40b exclude 0 values on EnumerateQuest 2024-02-19 17:37:40 +11:00
wish
7549fe63e6 conform Event Quest body size & auto change gathering points 2024-02-19 17:35:24 +11:00
wish
0b3e1f520f use Monster enum on GetPaperData 2024-02-19 17:34:08 +11:00
wish
377ff14a22 use Monster enum on GetPaperData 2024-02-19 17:34:01 +11:00
wish
18cabd03f1 add version case to FestaInfo 2024-02-19 17:32:32 +11:00
wish
79cdc28a01 simplify Gem math 2024-02-14 18:36:06 +11:00
wish
685f51ecb3 clean up Tower responses 2024-02-14 18:03:56 +11:00
wish
5f370896df clean up Tower responses 2024-02-14 17:28:01 +11:00
wish
6ec9d9d869 add AutoBackportQuest DebugOption 2024-02-11 23:22:42 +11:00
wish
771f240d13 implement course 31 2024-02-10 17:45:40 +11:00
wish
bbf4fa2472 further tune value configuration 2024-02-10 03:07:13 +11:00
wish
df9e33bdcc fix dirty pr 2024-02-08 18:29:04 +11:00
stratic-dev
86f79e4331 Merge pull request #112 from ZeruLight/docker
Docker support and Authors
2024-02-01 21:59:57 +00:00
stratic-dev
b4639b628b Create AUTHORS.md 2024-02-01 21:56:43 +00:00
stratic-dev
08e6fd2cda Delete 9.2-init.sql 2024-02-01 21:54:40 +00:00
wish
1bbcae395b Merge pull request #113 from sasospanner/Config-mismatch 2024-01-30 15:58:07 +11:00
sasospanner
20cc4c2a9e fix config mismatch 2024-01-29 21:58:36 -05:00
stratic-dev
851301b088 Updated readme and added init schemas to folder 2024-01-26 17:59:34 +00:00
stratic-dev
abe4744587 Added db init script. Combined all schemas under schemas. Persisted
updates and init
2024-01-24 04:35:24 +00:00
stratic-dev
463ceba555 Added github action 2024-01-22 22:45:31 +00:00
stratic-dev
76e62c6af2 Added readme and comments to docker-compose 2024-01-22 22:35:45 +00:00
stratic-dev
4a7f7b8041 Added docker and docker-compose 2024-01-22 20:06:40 +00:00
wish
59eafbe3d5 fix EnumerateQuest rotation 2024-01-16 19:50:41 +11:00
wish
6b54e40cc6 add tuneValue comments 2024-01-14 14:13:11 +11:00
wish
1a6a9da308 add logging to EnumerateQuest 2024-01-14 14:09:51 +11:00
wish
4fbfd569df Merge pull request #111 from ZeruLight/feature/moderation
feature/moderation
2024-01-11 23:04:29 +11:00
wish
af29ee637e minor session optimisations 2024-01-11 23:03:53 +11:00
wish
2685476022 check against unwrapped error 2024-01-11 23:01:53 +11:00
wish
a7bf38388c fix package collision 2024-01-11 23:01:32 +11:00
wish
4ccb3af5ac simplify cryptography 2024-01-11 23:00:44 +11:00
wish
ba7321b535 fix stringstack & MoveStage error 2024-01-10 19:29:51 +11:00
wish
c8e21387c0 rewrite CastBinary payload handling 2024-01-06 17:43:25 +11:00
wish
f73bdd7445 rewrite CastBinary payload handling 2024-01-05 02:39:25 +11:00
wish
2135c443d8 add ban chat command 2024-01-03 19:30:57 +11:00
wish
e0615dcd0c add support for operator accounts & bans 2024-01-03 19:08:45 +11:00
wish
8cd114988d Merge pull request #110 from ZeruLight/feature/i18n
i18n proposal
2024-01-03 15:43:17 +11:00
wish
ca80a98141 i18n proposal 2024-01-03 04:22:25 +11:00
wish
1766b6f2bd rewrite EntranceServer response 2024-01-02 20:04:40 +11:00
wish
ca09c24656 Merge pull request #108 from ZeruLight/dependabot/go_modules/golang.org/x/crypto-0.17.0
Bump golang.org/x/crypto from 0.15.0 to 0.17.0
2024-01-02 19:58:31 +11:00
wish
d6322e2f80 Merge pull request #103 from matthe815/feature/discord-login
feat: Password resetting by Discord integration
2024-01-02 19:58:02 +11:00
wish
63388aa4f7 re-index patch-schema 2024-01-02 19:53:27 +11:00
wish
1ed8b97347 update onInteraction process 2024-01-01 23:39:29 +11:00
wish
0d28637095 support long messages, rename to RelayChannel, move commands out of main 2024-01-01 21:22:51 +11:00
wish
a602bda47b reuse existing token, JP string & command description 2024-01-01 02:10:27 +11:00
wish
4f5eeb1508 add ico resources 2024-01-01 01:03:20 +11:00
wish
b3a265e218 Merge remote-tracking branch 'origin/main' into feature/discord-login 2023-12-31 22:38:08 +11:00
wish
3db6ee7b25 simplify EntranceServer crypto 2023-12-31 19:56:20 +11:00
wish
57bf1ca6e4 simplify EntranceServer crypto 2023-12-31 19:12:33 +11:00
wish
1aa2e36087 simplify World name concatenation 2023-12-31 16:30:11 +11:00
wish
5a8bc3b67a fix EntranceServer response on G5 2023-12-31 15:12:20 +11:00
wish
32dee9039e simplify config 2023-12-31 12:54:18 +11:00
wish
0ea0dc217b simplify config 2023-12-31 12:51:24 +11:00
wish
52082aaf06 use correct GuildMember length 2023-12-31 11:43:10 +11:00
wish
0069a5029f decode Festa stuff 2023-12-31 11:42:44 +11:00
wish
fd02a12ae9 add DefaultCourses DevModeOption 2023-12-31 10:22:27 +11:00
Matthew
293122b9a1 rm: Unintentional file inclusion 2023-12-20 13:47:08 -08:00
dependabot[bot]
c715578519 Bump golang.org/x/crypto from 0.15.0 to 0.17.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 23:59:56 +00:00
wish
b8f431ae66 parse CapLink responses 2023-12-17 17:47:35 +11:00
wish
345126ffcb fix warehouseGetEquipment 2023-12-13 11:50:29 +11:00
wish
7e34862e25 Merge remote-tracking branch 'origin/main' into feature/warehouse-v2
# Conflicts:
#	server/channelserver/handlers.go
2023-12-13 11:38:05 +11:00
wish
6686d59146 fix UdSchedule debug response 2023-12-08 10:47:34 +11:00
wish
24eed13a7c Merge pull request #107 from ZeruLight/fix/goocoo
fix/goocoo
2023-12-05 23:50:56 +11:00
wish
a7ac2c0672 remove comment 2023-12-05 23:48:47 +11:00
Matthew
526a9504bb Merge branch 'feature/discord-login' of https://github.com/matthe815/Erupe into feature/discord-login 2023-12-04 13:48:32 -05:00
Matthew
26438306c6 fix: Missing closing bracket 2023-12-04 13:48:17 -05:00
Matthew
c1b9c75138 Merge branch 'ZeruLight:main' into feature/discord-login 2023-12-04 10:45:26 -08:00
wish
ef7d46ba2e revert UpdateGuacot parsing 2023-12-04 22:29:58 +11:00
wish
7717f2f12a fix Enumerate/UpdateGuacot 2023-12-04 22:06:43 +11:00
wish
4844acee9c fix UpdateEtcPoint logic 2023-12-04 20:10:14 +11:00
Matthew
b7c5fe70e7 Merge branch 'main' into feature/discord-login 2023-11-29 10:52:01 -08:00
Matthew
eeeb998040 Merge branch 'ZeruLight:main' into feature/discord-login 2023-11-27 12:44:25 -08:00
Matthew
a77d6d53aa refactor: Remove reverted mutex changes 2023-11-27 03:27:20 -05:00
Matthew
7d630088a4 refactor: Fix code formatting and reset config 2023-11-27 03:23:30 -05:00
Matthew
a4745f05d5 refactor: Clean up random token implementation 2023-11-27 03:03:48 -05:00
Matthew
523266fc68 refactor: Move realtime channels to its own config 2023-11-27 02:18:01 -05:00
Matthew
38b57c6d98 refactor: Change to using rand.Read instead of whatever the hell else was before 2023-11-27 01:08:40 -05:00
Matthew
d2e9e3d1a9 fix: Fix default config (whoops) 2023-11-26 16:49:11 -05:00
Matthew
226adddc43 feat: Generate hashes for Discord and allow password resets 2023-11-26 16:47:54 -05:00
Matthew
50946b9c68 Merge branch 'ZeruLight:main' into feature/discord-login 2023-11-26 09:18:54 -08:00
Matthew
33665130cf feat: Discord basic implementation 2023-11-26 01:22:51 -05:00
wish
a9f280a2ef initial warehouse-v2 concept commit 2023-10-01 03:17:51 +11:00
126 changed files with 5158 additions and 4302 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 }}

4
.gitignore vendored
View File

@@ -6,4 +6,6 @@ vendor/
savedata/*/
*.exe
*.lnk
*.bat
*.bat
/docker/db-data
screenshots/*

30
AUTHORS.md Normal file
View File

@@ -0,0 +1,30 @@
# List of authors who contributed to Erupe
## 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)
* sekaiwish Fork, 2022 (https://github.com/sekaiwish/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 the divergence between Ellie42's branch and xl3lackout's where history has been lost.
Unfortunately, we have no detailed information on the history of Erupe before 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

@@ -1,6 +1,6 @@
# Erupe
## Client Compatiblity
## Client Compatibility
### Platforms
- PC
- PlayStation 3
@@ -32,6 +32,20 @@ If you want to modify or compile Erupe yourself, please read on.
3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup.
4. Run `go build` or `go run .` to compile Erupe.
## Docker
Please see [docker/README.md](./docker/README.md). This is intended for quick installs and development, not for production.
## Schemas
We source control the following schemas:
- Initialization Schema: This initializes the application database to a specific version (9.1.0).
- Update Schemas: These are update files that should be ran on top of the initialization schema.
- Patch Schemas: These are for development and should be run after running all initialization and update schema. These get condensed into `Update Schemas` and deleted when updated to a new release.
- Bundled Schemas: These are demo reference files to give servers standard set-ups.
Note: Patch schemas are subject to change! You should only be using them if you are following along with development.
## Resources
- [Quest and Scenario Binary Files](https://files.catbox.moe/xf0l7w.7z)

File diff suppressed because it is too large Load Diff

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

175
common/mhfitem/mhfitem.go Normal file
View File

@@ -0,0 +1,175 @@
package mhfitem
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/token"
_config "erupe-ce/config"
)
type MHFItem struct {
ItemID uint16
}
type MHFSigilEffect struct {
ID uint16
Level uint16
}
type MHFSigil struct {
Effects []MHFSigilEffect
Unk0 uint8
Unk1 uint8
Unk2 uint8
Unk3 uint8
}
type MHFEquipment struct {
WarehouseID uint32
ItemType uint8
Unk0 uint8
ItemID uint16
Level uint16
Decorations []MHFItem
Sigils []MHFSigil
Unk1 uint16
}
type MHFItemStack struct {
WarehouseID uint32
Item MHFItem
Quantity uint16
Unk0 uint32
}
func ReadWarehouseItem(bf *byteframe.ByteFrame) MHFItemStack {
var item MHFItemStack
item.WarehouseID = bf.ReadUint32()
if item.WarehouseID == 0 {
item.WarehouseID = token.RNG.Uint32()
}
item.Item.ItemID = bf.ReadUint16()
item.Quantity = bf.ReadUint16()
item.Unk0 = bf.ReadUint32()
return item
}
func DiffItemStacks(o []MHFItemStack, u []MHFItemStack) []MHFItemStack {
// o = old, u = update, f = final
var f []MHFItemStack
for _, uItem := range u {
exists := false
for i := range o {
if o[i].WarehouseID == uItem.WarehouseID {
exists = true
o[i].Quantity = uItem.Quantity
}
}
if !exists {
uItem.WarehouseID = token.RNG.Uint32()
f = append(f, uItem)
}
}
for _, oItem := range o {
if oItem.Quantity > 0 {
f = append(f, oItem)
}
}
return f
}
func (is MHFItemStack) ToBytes() []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint32(is.WarehouseID)
bf.WriteUint16(is.Item.ItemID)
bf.WriteUint16(is.Quantity)
bf.WriteUint32(is.Unk0)
return bf.Data()
}
func SerializeWarehouseItems(i []MHFItemStack) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(i)))
bf.WriteUint16(0) // Unused
for _, j := range i {
bf.WriteBytes(j.ToBytes())
}
return bf.Data()
}
func ReadWarehouseEquipment(bf *byteframe.ByteFrame) MHFEquipment {
var equipment MHFEquipment
equipment.Decorations = make([]MHFItem, 3)
equipment.Sigils = make([]MHFSigil, 3)
for i := 0; i < 3; i++ {
equipment.Sigils[i].Effects = make([]MHFSigilEffect, 3)
}
equipment.WarehouseID = bf.ReadUint32()
if equipment.WarehouseID == 0 {
equipment.WarehouseID = token.RNG.Uint32()
}
equipment.ItemType = bf.ReadUint8()
equipment.Unk0 = bf.ReadUint8()
equipment.ItemID = bf.ReadUint16()
equipment.Level = bf.ReadUint16()
for i := 0; i < 3; i++ {
equipment.Decorations[i].ItemID = bf.ReadUint16()
}
if _config.ErupeConfig.RealClientMode >= _config.G1 {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
equipment.Sigils[i].Effects[j].ID = bf.ReadUint16()
}
for j := 0; j < 3; j++ {
equipment.Sigils[i].Effects[j].Level = bf.ReadUint16()
}
equipment.Sigils[i].Unk0 = bf.ReadUint8()
equipment.Sigils[i].Unk1 = bf.ReadUint8()
equipment.Sigils[i].Unk2 = bf.ReadUint8()
equipment.Sigils[i].Unk3 = bf.ReadUint8()
}
}
if _config.ErupeConfig.RealClientMode >= _config.Z1 {
equipment.Unk1 = bf.ReadUint16()
}
return equipment
}
func (e MHFEquipment) ToBytes() []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint32(e.WarehouseID)
bf.WriteUint8(e.ItemType)
bf.WriteUint8(e.Unk0)
bf.WriteUint16(e.ItemID)
bf.WriteUint16(e.Level)
for i := 0; i < 3; i++ {
bf.WriteUint16(e.Decorations[i].ItemID)
}
if _config.ErupeConfig.RealClientMode >= _config.G1 {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
bf.WriteUint16(e.Sigils[i].Effects[j].ID)
}
for j := 0; j < 3; j++ {
bf.WriteUint16(e.Sigils[i].Effects[j].Level)
}
bf.WriteUint8(e.Sigils[i].Unk0)
bf.WriteUint8(e.Sigils[i].Unk1)
bf.WriteUint8(e.Sigils[i].Unk2)
bf.WriteUint8(e.Sigils[i].Unk3)
}
}
if _config.ErupeConfig.RealClientMode >= _config.Z1 {
bf.WriteUint16(e.Unk1)
}
return bf.Data()
}
func SerializeWarehouseEquipment(i []MHFEquipment) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(i)))
bf.WriteUint16(0) // Unused
for _, j := range i {
bf.WriteBytes(j.ToBytes())
}
return bf.Data()
}

View File

@@ -6,8 +6,7 @@ import (
// StringStack is a basic LIFO "stack" for storing strings.
type StringStack struct {
Locked bool
stack []string
stack []string
}
// New creates a new instance of StringStack
@@ -20,20 +19,6 @@ func (s *StringStack) Set(v string) {
s.stack = []string{v}
}
// Lock freezes the StringStack
func (s *StringStack) Lock() {
if !s.Locked {
s.Locked = true
}
}
// Unlock unfreezes the StringStack
func (s *StringStack) Unlock() {
if s.Locked {
s.Locked = false
}
}
// Push pushes a string onto the stack.
func (s *StringStack) Push(v string) {
s.stack = append(s.stack, v)

View File

@@ -1,30 +1,23 @@
package token
import (
crand "crypto/rand"
"math/rand"
"time"
)
var RNG = NewRNG()
// Generate returns an alphanumeric token of specified length
func Generate(length int) string {
rng := RNG()
var chars = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, length)
for i := range b {
b[i] = chars[rng.Intn(len(chars))]
b[i] = chars[RNG.Intn(len(chars))]
}
return string(b)
}
// RNG returns a new RNG generator
func RNG() *rand.Rand {
// NewRNG returns a new NewRNG generator
func NewRNG() *rand.Rand {
return rand.New(rand.NewSource(time.Now().UnixNano()))
}
// RandBytes returns x random bytes
func RandBytes(x int) []byte {
y := make([]byte, x)
crand.Read(y)
return y
}

View File

@@ -9,37 +9,50 @@
],
"PatchServerManifest": "",
"PatchServerFile": "",
"ScreenshotAPIURL": "",
"Screenshots":{
"Enabled":true,
"Host":"127.0.0.1",
"Port":8080,
"OutputDir":"screenshots",
"UploadQuality":100
},
"DeleteOnSaveCorruption": false,
"ClientMode": "ZZ",
"ClientMode": "G9.1",
"QuestCacheExpiry": 300,
"ProxyPort": 0,
"CommandPrefix": "!",
"DevMode": true,
"DevModeOptions": {
"AutoCreateAccount": true,
"AutoCreateAccount": true,
"DefaultCourses": [1, 23, 24],
"EarthDebug": false,
"EarthMonsters": [116, 107, 2, 36],
"SaveDumps": {
"Enabled": true,
"RawEnabled": false,
"OutputDir": "save-backups"
},
"DebugOptions": {
"CleanDB": false,
"MaxLauncherHR": false,
"LogInboundMessages": false,
"LogOutboundMessages": false,
"LogMessageData": false,
"MaxHexdumpLength": 256,
"DivaEvent": 0,
"FestaEvent": -1,
"TournamentEvent": 0,
"DivaOverride": 0,
"FestaOverride": -1,
"TournamentOverride": 0,
"DisableTokenCheck": false,
"QuestDebugTools": false,
"EarthStatusOverride": 0,
"EarthIDOverride": 0,
"EarthMonsterOverride": [0, 0, 0, 0],
"SaveDumps": {
"Enabled": true,
"RawEnabled": false,
"OutputDir": "save-backups"
"QuestTools": false,
"AutoQuestBackport": true,
"ProxyPort": 0,
"CapLink": {
"Values": [51728, 20000, 51729, 1, 20000],
"Key": "",
"Host": "127.0.0.1",
"Port": 8080
}
},
"GameplayOptions": {
"FeaturedWeapons": 1,
"MinFeatureWeapons": 0,
"MaxFeatureWeapons": 1,
"MaximumNP": 100000,
"MaximumRP": 50000,
"MaximumFP": 120000,
@@ -51,8 +64,6 @@
"ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]],
"BonusQuestAllowance": 3,
"DailyQuestAllowance": 1,
"MezfesSoloTickets": 10,
"MezfesGroupTickets": 4,
"LowLatencyRaviente": false,
"RegularRavienteMaxPlayers": 8,
"ViolentRavienteMaxPlayers": 8,
@@ -61,12 +72,29 @@
"SmallBerserkRavienteMaxPlayers": 8,
"GUrgentRate": 0.10,
"GCPMultiplier": 1.00,
"HRPMultiplier": 1.00,
"HRPMultiplierNC": 1.00,
"SRPMultiplier": 1.00,
"SRPMultiplierNC": 1.00,
"GRPMultiplier": 1.00,
"GRPMultiplierNC": 1.00,
"GSRPMultiplier": 1.00,
"GSRPMultiplierNC": 1.00,
"ZennyMultiplier": 1.00,
"ZennyMultiplierNC": 1.00,
"GZennyMultiplier": 1.00,
"GZennyMultiplierNC": 1.00,
"MaterialMultiplier": 1.00,
"MaterialMultiplierNC": 1.00,
"GMaterialMultiplier": 1.00,
"GMaterialMultiplierNC": 1.00,
"ExtraCarves": 0,
"ExtraCarvesNC": 0,
"GExtraCarves": 0,
"GExtraCarvesNC": 0,
"DisableHunterNavi": false,
"MezFesSoloTickets": 5,
"MezFesGroupTickets": 1,
"MezFesDuration": 172800,
"MezFesSwitchMinigame": false,
"EnableKaijiEvent": false,
@@ -78,7 +106,11 @@
"Discord": {
"Enabled": false,
"BotToken": "",
"RealtimeChannelID": ""
"RelayChannel": {
"Enabled": false,
"MaxMessageLength": 183,
"RelayChannelID": ""
}
},
"Commands": [
{
@@ -121,6 +153,21 @@
"Enabled": true,
"Description": "Link a PlayStation Network ID to your account",
"Prefix": "psn"
}, {
"Name": "Discord",
"Enabled": true,
"Description": "Generate a token to link your Discord account",
"Prefix": "discord"
}, {
"Name": "Ban",
"Enabled": false,
"Description": "Ban/Temp Ban a user",
"Prefix": "ban"
}, {
"Name": "Timer",
"Enabled": true,
"Description": "Toggle the Quest timer",
"Prefix": "timer"
}
],
"Courses": [
@@ -147,8 +194,8 @@
"Enabled": true,
"Port": 53312
},
"SignV2": {
"Enabled": false,
"API": {
"Enabled": true,
"Port": 8080,
"PatchServer": "",
"Banners": [],
@@ -156,37 +203,45 @@
"Links": []
},
"Channel": {
"Enabled": true,
"Worlds": [
{
"Name": "Novice", "Description": "Up to 2★", "IP": "", "Type": 3, "Recommended": 1, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}]
}, {
"Name": "Rookie", "Description": "Up to 4★", "IP": "", "Type": 3, "Recommended": 2, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}]
}, {
"Name": "Sincere", "Description": "All Quests", "IP": "", "Type": 1, "Recommended": 0, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}]
}, {
"Name": "Brave", "Description": "Only G Quests", "IP": "", "Type": 1, "Recommended": 5, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}]
}, {
"Name": "Noble", "Description": "All Quests", "IP": "", "Type": 2, "Recommended": 0, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}, {"MaxPlayers": 100}]
}, {
"Name": "Spirit", "Description": "All Quests", "IP": "", "Type": 4, "Recommended": 0, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}, {"MaxPlayers": 100}]
}, {
"Name": "Legend", "Description": "All Quests", "IP": "", "Type": 5, "Recommended": 0, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 100}]
}, {
"Name": "Fancy", "Description": "Minigames!", "IP": "", "Type": 6, "Recommended": 6, "AllowedClientFlags": 0,
"Lands": [{"MaxPlayers": 80}]
}
]
"Enabled": true
},
"Entrance": {
"Enabled": true,
"Port": 53310
"Port": 53310,
"Entries": [
{
"Name": "Newbie", "Description": "", "IP": "", "Type": 3, "Recommended": 2, "AllowedClientFlags": 0,
"Channels": [
{ "Port": 54001, "MaxPlayers": 100 },
{ "Port": 54002, "MaxPlayers": 100 }
]
}, {
"Name": "Normal", "Description": "", "IP": "", "Type": 1, "Recommended": 0, "AllowedClientFlags": 0,
"Channels": [
{ "Port": 54003, "MaxPlayers": 100 },
{ "Port": 54004, "MaxPlayers": 100 }
]
}, {
"Name": "Cities", "Description": "", "IP": "", "Type": 2, "Recommended": 0, "AllowedClientFlags": 0,
"Channels": [
{ "Port": 54005, "MaxPlayers": 100 }
]
}, {
"Name": "Tavern", "Description": "", "IP": "", "Type": 4, "Recommended": 0, "AllowedClientFlags": 0,
"Channels": [
{ "Port": 54006, "MaxPlayers": 100 }
]
}, {
"Name": "Return", "Description": "", "IP": "", "Type": 5, "Recommended": 0, "AllowedClientFlags": 0,
"Channels": [
{ "Port": 54007, "MaxPlayers": 100 }
]
}, {
"Name": "MezFes", "Description": "", "IP": "", "Type": 6, "Recommended": 6, "AllowedClientFlags": 0,
"Channels": [
{ "Port": 54008, "MaxPlayers": 100 }
]
}
]
}
}

View File

@@ -75,56 +75,73 @@ type Config struct {
LoginNotices []string // MHFML string of the login notices displayed
PatchServerManifest string // Manifest patch server override
PatchServerFile string // File patch server override
ScreenshotAPIURL string // Destination for screenshots uploaded to BBS
DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion
ClientMode string
RealClientMode Mode
QuestCacheExpiry int // Number of seconds to keep quest data cached
ProxyPort uint16 // Forces the game to connect to a channel server proxy
CommandPrefix string // The prefix for commands
DevMode bool
AutoCreateAccount bool // Automatically create accounts if they don't exist
DefaultCourses []uint16
EarthDebug bool
EarthMonsters []int32
SaveDumps SaveDumpOptions
Screenshots ScreenshotsOptions
DevModeOptions DevModeOptions
DebugOptions DebugOptions
GameplayOptions GameplayOptions
Discord Discord
Commands []Command
Courses []Course
Database Database
Sign Sign
SignV2 SignV2
API API
Channel Channel
Entrance Entrance
}
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
type DevModeOptions struct {
AutoCreateAccount bool // Automatically create accounts if they don't exist
CleanDB bool // Automatically wipes the DB on server reset.
MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
LogInboundMessages bool // Log all messages sent to the server
LogOutboundMessages bool // Log all messages sent to the clients
LogMessageData bool // Log all bytes transferred as a hexdump
MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
DivaEvent int // Diva Defense event status
FestaEvent int // Hunter's Festa event status
TournamentEvent int // VS Tournament event status
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
QuestDebugTools bool // Enable various quest debug logs
EarthStatusOverride int32
EarthIDOverride int32
EarthMonsterOverride []int32
SaveDumps SaveDumpOptions
}
type SaveDumpOptions struct {
Enabled bool
RawEnabled bool
OutputDir string
}
type ScreenshotsOptions struct {
Enabled bool
Host string // Destination for screenshots uploaded to BBS
Port uint32 // Port for screenshots API
OutputDir string
UploadQuality int //Determines the upload quality to the server
}
// DebugOptions holds various debug/temporary options for use while developing Erupe.
type DebugOptions struct {
CleanDB bool // Automatically wipes the DB on server reset.
MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
LogInboundMessages bool // Log all messages sent to the server
LogOutboundMessages bool // Log all messages sent to the clients
LogMessageData bool // Log all bytes transferred as a hexdump
MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
DivaOverride int // Diva Defense event status
FestaOverride int // Hunter's Festa event status
TournamentOverride int // VS Tournament event status
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
QuestTools bool // Enable various quest debug logs
AutoQuestBackport bool // Automatically backport quest files
ProxyPort uint16 // Forces the game to connect to a channel server proxy
CapLink CapLinkOptions
}
type CapLinkOptions struct {
Values []uint16
Key string
Host string
Port int
}
// GameplayOptions has various gameplay modifiers
type GameplayOptions struct {
FeaturedWeapons int // Number of Active Feature weapons to generate daily
MinFeatureWeapons int // Minimum number of Active Feature weapons to generate daily
MaxFeatureWeapons int // Maximum number of Active Feature weapons to generate daily
MaximumNP int // Maximum number of NP held by a player
MaximumRP uint16 // Maximum number of RP held by a player
MaximumFP uint32 // Maximum number of FP held by a player
@@ -137,8 +154,6 @@ type GameplayOptions struct {
ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members]
BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily
DailyQuestAllowance uint32 // Number of Daily Quests to allow daily
MezfesSoloTickets uint32 // Number of solo tickets given weekly
MezfesGroupTickets uint32 // Number of group tickets given weekly
LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive
RegularRavienteMaxPlayers uint8
ViolentRavienteMaxPlayers uint8
@@ -147,12 +162,29 @@ type GameplayOptions struct {
SmallBerserkRavienteMaxPlayers uint8
GUrgentRate float32 // Adjusts the rate of G Urgent quests spawning
GCPMultiplier float32 // Adjusts the multiplier of GCP rewarded for quest completion
HRPMultiplier float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion
HRPMultiplierNC float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion in a NetCafe
SRPMultiplier float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion
SRPMultiplierNC float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion in a NetCafe
GRPMultiplier float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion
GRPMultiplierNC float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion in a NetCafe
GSRPMultiplier float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion
GSRPMultiplierNC float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion in a NetCafe
ZennyMultiplier float32 // Adjusts the multiplier of Zenny rewarded for quest completion
ZennyMultiplierNC float32 // Adjusts the multiplier of Zenny rewarded for quest completion in a NetCafe
GZennyMultiplier float32 // Adjusts the multiplier of G Zenny rewarded for quest completion
GZennyMultiplierNC float32 // Adjusts the multiplier of G Zenny rewarded for quest completion in a NetCafe
MaterialMultiplier float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion
MaterialMultiplierNC float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion in a NetCafe
GMaterialMultiplier float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion
GMaterialMultiplierNC float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion in a NetCafe
ExtraCarves uint16 // Grant n extra chances to carve ALL carcasses
ExtraCarvesNC uint16 // Grant n extra chances to carve ALL carcasses in a NetCafe
GExtraCarves uint16 // Grant n extra chances to carve ALL G Rank carcasses
GExtraCarvesNC uint16 // Grant n extra chances to carve ALL G Rank carcasses in a NetCafe
DisableHunterNavi bool // Disables the Hunter Navi
MezFesSoloTickets uint32 // Number of solo tickets given weekly
MezFesGroupTickets uint32 // Number of group tickets given weekly
MezFesDuration int // Seconds that MezFes will last for weekly (from 12AM Mon backwards)
MezFesSwitchMinigame bool // Swaps out Volpakkun Together for Tokotoko Partnya
EnableKaijiEvent bool // Enables the Kaiji event in the Rasta Bar
@@ -164,9 +196,15 @@ type GameplayOptions struct {
// Discord holds the discord integration config.
type Discord struct {
Enabled bool
BotToken string
RealtimeChannelID string
Enabled bool
BotToken string
RelayChannel DiscordRelay
}
type DiscordRelay struct {
Enabled bool
MaxMessageLength int
RelayChannelID string
}
// Command is a channelserver chat command
@@ -198,29 +236,29 @@ type Sign struct {
Port int
}
// SignV2 holds the new sign server config
type SignV2 struct {
// API holds server config
type API struct {
Enabled bool
Port int
PatchServer string
Banners []SignV2Banner
Messages []SignV2Message
Links []SignV2Link
Banners []APISignBanner
Messages []APISignMessage
Links []APISignLink
}
type SignV2Banner struct {
type APISignBanner struct {
Src string `json:"src"` // Displayed image URL
Link string `json:"link"` // Link accessed on click
}
type SignV2Message struct {
type APISignMessage struct {
Message string `json:"message"` // Displayed message
Date int64 `json:"date"` // Displayed date
Kind int `json:"kind"` // 0 for 'Default', 1 for 'New'
Link string `json:"link"` // Link accessed on click
}
type SignV2Link struct {
type APISignLink struct {
Name string `json:"name"` // Displayed name
Icon string `json:"icon"` // Displayed icon. It will be cast as a monochrome color as long as it is transparent.
Link string `json:"link"` // Link accessed on click
@@ -228,29 +266,35 @@ type SignV2Link struct {
type Channel struct {
Enabled bool
Worlds []World
}
type World struct {
IP string
Type uint8
Season uint8
Recommended uint8
Name string
Description string
AllowedClientFlags uint32
Lands []Land
}
type Land struct {
Port uint16
MaxPlayers uint16
}
// Entrance holds the entrance server config.
type Entrance struct {
Enabled bool
Port uint16
Entries []EntranceServerInfo
}
// EntranceServerInfo represents an entry in the serverlist.
type EntranceServerInfo struct {
IP string
Type uint8 // Server type. 0=?, 1=open, 2=cities, 3=newbie, 4=bar
Season uint8 // Server activity. 0 = green, 1 = orange, 2 = blue
Recommended uint8 // Something to do with server recommendation on 0, 3, and 5.
Name string // Server name, 66 byte null terminated Shift-JIS(JP) or Big5(TW).
Description string // Server description
// 4096(PC, PS3/PS4)?, 8258(PC, PS3/PS4)?, 8192 == nothing?
// THIS ONLY EXISTS IF Binary8Header.type == "SV2", NOT "SVR"!
AllowedClientFlags uint32
Channels []EntranceChannelInfo
}
// EntranceChannelInfo represents an entry in a server's channel list.
type EntranceChannelInfo struct {
Port uint16
MaxPlayers uint16
CurrentPlayers uint16
}
var ErupeConfig *Config
@@ -316,6 +360,10 @@ func LoadConfig() (*Config, error) {
c.RealClientMode = ZZ
}
if c.GameplayOptions.MinFeatureWeapons > c.GameplayOptions.MaxFeatureWeapons {
c.GameplayOptions.MinFeatureWeapons = c.GameplayOptions.MaxFeatureWeapons
}
return c, nil
}

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

8
go.sum
View File

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

111
main.go
View File

@@ -7,15 +7,14 @@ import (
"os"
"os/signal"
"runtime/debug"
"slices"
"syscall"
"time"
"erupe-ce/server/api"
"erupe-ce/server/channelserver"
"erupe-ce/server/discordbot"
"erupe-ce/server/entranceserver"
"erupe-ce/server/signserver"
"erupe-ce/server/signv2server"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
@@ -23,13 +22,10 @@ import (
)
// Temporary DB auto clean on startup for quick development & testing.
func cleanDB(db *sqlx.DB, config *_config.Config) {
func cleanDB(db *sqlx.DB) {
_ = db.MustExec("DELETE FROM guild_characters")
_ = db.MustExec("DELETE FROM guilds")
_ = db.MustExec("DELETE FROM characters")
if config.ProxyPort == 0 {
_ = db.MustExec("DELETE FROM sign_sessions")
}
_ = db.MustExec("DELETE FROM users")
}
@@ -49,11 +45,7 @@ func main() {
var zapLogger *zap.Logger
config := _config.ErupeConfig
if config.DevMode {
zapLogger, _ = zap.NewDevelopment()
} else {
zapLogger, _ = zap.NewProduction()
}
zapLogger, _ = zap.NewDevelopment()
defer zapLogger.Sync()
logger := zapLogger.Named("main")
@@ -99,6 +91,12 @@ func main() {
}
discordBot = bot
_, err = discordBot.Session.ApplicationCommandBulkOverwrite(discordBot.Session.State.User.ID, "", discordbot.Commands)
if err != nil {
preventClose(fmt.Sprintf("Discord: Failed to start, %s", err.Error()))
}
logger.Info("Discord: Started successfully")
} else {
logger.Info("Discord: Disabled")
@@ -127,16 +125,16 @@ func main() {
logger.Info("Database: Started successfully")
// Clear stale data
if config.ProxyPort == 0 {
if config.DebugOptions.ProxyPort == 0 {
_ = db.MustExec("DELETE FROM sign_sessions")
}
_ = db.MustExec("DELETE FROM servers")
_ = db.MustExec(`UPDATE guild_characters SET treasure_hunt=NULL`)
// Clean the DB if the option is on.
if config.DevMode && config.DevModeOptions.CleanDB {
if config.DebugOptions.CleanDB {
logger.Info("Database: Started clearing...")
cleanDB(db, config)
cleanDB(db)
logger.Info("Database: Finished clearing")
}
@@ -151,7 +149,7 @@ func main() {
entranceServer = entranceserver.NewServer(
&entranceserver.Config{
Logger: logger.Named("entrance"),
ErupeConfig: config,
ErupeConfig: _config.ErupeConfig,
DB: db,
})
err = entranceServer.Start()
@@ -170,7 +168,7 @@ func main() {
signServer = signserver.NewServer(
&signserver.Config{
Logger: logger.Named("sign"),
ErupeConfig: config,
ErupeConfig: _config.ErupeConfig,
DB: db,
})
err = signServer.Start()
@@ -183,37 +181,37 @@ func main() {
}
// New Sign server
var newSignServer *signv2server.Server
if config.SignV2.Enabled {
newSignServer = signv2server.NewServer(
&signv2server.Config{
var ApiServer *api.APIServer
if config.API.Enabled {
ApiServer = api.NewAPIServer(
&api.Config{
Logger: logger.Named("sign"),
ErupeConfig: config,
ErupeConfig: _config.ErupeConfig,
DB: db,
})
err = newSignServer.Start()
err = ApiServer.Start()
if err != nil {
preventClose(fmt.Sprintf("SignV2: Failed to start, %s", err.Error()))
preventClose(fmt.Sprintf("API: Failed to start, %s", err.Error()))
}
logger.Info("SignV2: Started successfully")
logger.Info("API: Started successfully")
} else {
logger.Info("SignV2: Disabled")
logger.Info("API: Disabled")
}
var worlds [][]*channelserver.Server
var ports []uint16
var channels []*channelserver.Server
if config.Channel.Enabled {
channelQuery := ""
var count int
for j, ee := range config.Channel.Worlds {
var lands []*channelserver.Server
for i, ce := range ee.Lands {
sid := (4096 + j*256) + (16 + i)
si := 0
ci := 0
count := 1
for j, ee := range config.Entrance.Entries {
for i, ce := range ee.Channels {
sid := (4096 + si*256) + (16 + ci)
c := *channelserver.NewServer(&channelserver.Config{
ID: uint16(sid),
Logger: logger.Named("channel-" + fmt.Sprint(count+1)),
ErupeConfig: config,
Logger: logger.Named("channel-" + fmt.Sprint(count)),
ErupeConfig: _config.ErupeConfig,
DB: db,
DiscordBot: discordBot,
})
@@ -222,41 +220,28 @@ func main() {
} else {
c.IP = ee.IP
}
if ce.Port == 0 {
for i := 0; ; i++ {
port := uint16(54001 + i)
if !slices.Contains(ports, port) {
ce.Port = port
break
}
}
}
if slices.Contains(ports, ce.Port) {
preventClose("Channel: Failed to start, duplicate port")
break
} else {
ports = append(ports, ce.Port)
c.Port = ce.Port
}
c.Port = ce.Port
c.GlobalID = fmt.Sprintf("%02d%02d", j+1, i+1)
err = c.Start()
if err != nil {
preventClose(fmt.Sprintf("Channel: Failed to start, %s", err.Error()))
} else {
channelQuery += fmt.Sprintf(`INSERT INTO servers (server_id, current_players, world_name, world_description, land) VALUES (%d, 0, '%s', '%s', %d);`, sid, ee.Name, ee.Description, i+1)
lands = append(lands, &c)
logger.Info(fmt.Sprintf("Channel %d (%d): Started successfully", count, c.Port))
channels = append(channels, &c)
logger.Info(fmt.Sprintf("Channel %d (%d): Started successfully", count, ce.Port))
ci++
count++
}
}
worlds = append(worlds, lands)
ci = 0
si++
}
// Register all servers in DB
_ = db.MustExec(channelQuery)
if config.Entrance.Enabled {
entranceServer.SetWorlds(worlds)
for _, c := range channels {
c.Channels = channels
}
}
@@ -270,10 +255,8 @@ func main() {
if !config.DisableSoftCrash {
for i := 0; i < 10; i++ {
message := fmt.Sprintf("Shutting down in %d...", 10-i)
for _, w := range worlds {
for _, l := range w {
l.BroadcastChatMessage(message)
}
for _, c := range channels {
c.BroadcastChatMessage(message)
}
logger.Info(message)
time.Sleep(time.Second)
@@ -281,10 +264,8 @@ func main() {
}
if config.Channel.Enabled {
for _, w := range worlds {
for _, l := range w {
l.Shutdown()
}
for _, c := range channels {
c.Shutdown()
}
}
@@ -292,8 +273,8 @@ func main() {
signServer.Shutdown()
}
if config.SignV2.Enabled {
newSignServer.Shutdown()
if config.API.Enabled {
ApiServer.Shutdown()
}
if config.Entrance.Enabled {

View File

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

View File

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

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

View File

@@ -65,7 +65,7 @@ func TestEncrypt(t *testing.T) {
for k, tt := range tests {
testname := fmt.Sprintf("encrypt_test_%d", k)
t.Run(testname, func(t *testing.T) {
out, cc, c0, c1, c2 := Encrypt(tt.decryptedData, tt.key, nil)
out, cc, c0, c1, c2 := Crypto(tt.decryptedData, tt.key, true, nil)
if cc != tt.ecc {
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
} else if c0 != tt.ec0 {
@@ -86,7 +86,7 @@ func TestDecrypt(t *testing.T) {
for k, tt := range tests {
testname := fmt.Sprintf("decrypt_test_%d", k)
t.Run(testname, func(t *testing.T) {
out, cc, c0, c1, c2 := Decrypt(tt.encryptedData, tt.key, nil)
out, cc, c0, c1, c2 := Crypto(tt.decryptedData, tt.key, false, nil)
if cc != tt.ecc {
t.Errorf("got cc 0x%X, want 0x%X", cc, tt.ecc)
} else if c0 != tt.ec0 {

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -27,6 +28,8 @@ func (m *MsgMhfCaravanMyRank) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32()
fmt.Printf("MsgMhfCaravanMyRank: Unk0:[%d] Unk1:[%d] Unk2:[%d] \n\n", m.Unk0, m.Unk1, m.Unk2)
return nil
}

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -35,6 +36,8 @@ func (m *MsgMhfCaravanMyScore) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli
m.Unk4 = bf.ReadUint32()
m.Unk5 = bf.ReadInt32()
m.Unk6 = bf.ReadInt32()
fmt.Printf("MsgMhfCaravanMyScore: Unk0:[%d] Unk1:[%d] Unk2:[%d] Unk3:[%d] Unk4:[%d] Unk5:[%d] Unk6:[%d] \n", m.Unk0, m.Unk1, m.Unk2, m.Unk3, m.Unk4, m.Unk5, m.Unk6)
return nil
}

View File

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

View File

@@ -1,19 +1,20 @@
package mhfpacket
import (
"errors"
"errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfChargeFesta represents the MSG_MHF_CHARGE_FESTA
type MsgMhfChargeFesta struct {
AckHandle uint32
FestaID uint32
GuildID uint32
Souls int
AckHandle uint32
FestaID uint32
GuildID uint32
Souls []uint16
Auto bool
}
// Opcode returns the ID associated with this packet type.
@@ -23,15 +24,14 @@ func (m *MsgMhfChargeFesta) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfChargeFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.FestaID = bf.ReadUint32()
m.GuildID = bf.ReadUint32()
m.Souls = 0
for i := bf.ReadUint16(); i > 0; i-- {
m.Souls += int(bf.ReadUint16())
}
_ = bf.ReadUint8() // Unk
return nil
m.AckHandle = bf.ReadUint32()
m.FestaID = bf.ReadUint32()
m.GuildID = bf.ReadUint32()
for i := bf.ReadUint16(); i > 0; i-- {
m.Souls = append(m.Souls, bf.ReadUint16())
}
m.Auto = bf.ReadBool()
return nil
}
// Build builds a binary packet from the current data.

View File

@@ -11,7 +11,7 @@ import (
// MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE
type MsgMhfEnumerateWarehouse struct {
AckHandle uint32
BoxType string
BoxType uint8
BoxIndex uint8
}
@@ -23,15 +23,10 @@ func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
boxType := bf.ReadUint8()
switch boxType {
case 0:
m.BoxType = "item"
case 1:
m.BoxType = "equip"
}
m.BoxType = bf.ReadUint8()
m.BoxIndex = bf.ReadUint8()
_ = bf.ReadUint16()
bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed
return nil
}

View File

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

View File

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

View File

@@ -1,20 +1,21 @@
package mhfpacket
import (
"errors"
import (
"errors"
"fmt"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// MsgMhfGetPaperData represents the MSG_MHF_GET_PAPER_DATA
type MsgMhfGetPaperData struct {
// Communicator type, multi-format. This might be valid for only one type.
AckHandle uint32
Unk0 uint32
Type uint32
Unk1 uint32
Unk2 uint32
ID uint32
}
// Opcode returns the ID associated with this packet type.
@@ -25,9 +26,11 @@ func (m *MsgMhfGetPaperData) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetPaperData) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.Type = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32()
m.ID = bf.ReadUint32()
fmt.Printf("MsgMhfGetPaperData: Type:[%d] Unk1:[%d] ID:[%d] \n\n", m.Type, m.Unk1, m.ID)
return nil
}

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -28,6 +29,8 @@ func (m *MsgMhfGetTinyBin) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientC
m.Unk0 = bf.ReadUint8()
m.Unk1 = bf.ReadUint8()
m.Unk2 = bf.ReadUint8()
fmt.Printf("MsgMhfGetTinyBin: Unk0:[%d] Unk1:[%d] Unk2:[%d] \n\n", m.Unk0, m.Unk1, m.Unk2)
return nil
}

View File

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

View File

@@ -13,7 +13,7 @@ import (
type MsgMhfOperateWarehouse struct {
AckHandle uint32
Operation uint8
BoxType string
BoxType uint8
BoxIndex uint8
Name string
}
@@ -27,17 +27,13 @@ func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Operation = bf.ReadUint8()
boxType := bf.ReadUint8()
switch boxType {
case 0:
m.BoxType = "item"
case 1:
m.BoxType = "equip"
}
m.BoxType = bf.ReadUint8()
m.BoxIndex = bf.ReadUint8()
_ = bf.ReadUint8() // lenName
_ = bf.ReadUint16() // Unk
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
lenName := bf.ReadUint8()
bf.ReadUint16() // Zeroed
if lenName > 0 {
m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
}
return nil
}

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -31,6 +32,8 @@ func (m *MsgMhfPostTinyBin) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
m.Unk2 = bf.ReadUint8()
m.Unk3 = bf.ReadUint8()
m.Data = bf.ReadBytes(uint(bf.ReadUint16()))
fmt.Printf("MsgMhfPostTinyBin: Unk0:[%d] Unk1:[%d] Unk2:[%d] Unk3:[%d] \n\n", m.Unk0, m.Unk1, m.Unk2, m.Unk3)
return nil
}

View File

@@ -20,7 +20,8 @@ type MsgMhfPostTowerInfo struct {
Unk6 int32
Unk7 int32
Block1 int32
Unk9 int64
TimeTaken int32
CID int32
}
// Opcode returns the ID associated with this packet type.
@@ -40,7 +41,8 @@ func (m *MsgMhfPostTowerInfo) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie
m.Unk6 = bf.ReadInt32()
m.Unk7 = bf.ReadInt32()
m.Block1 = bf.ReadInt32()
m.Unk9 = bf.ReadInt64()
m.TimeTaken = bf.ReadInt32()
m.CID = bf.ReadInt32()
return nil
}

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -10,15 +11,15 @@ import (
// MsgMhfPresentBox represents the MSG_MHF_PRESENT_BOX
type MsgMhfPresentBox struct {
AckHandle uint32
Unk0 uint32
Unk1 uint32
Unk2 uint32
Unk3 uint32
Unk4 uint32
Unk5 uint32
Unk6 uint32
Unk7 []uint32
AckHandle uint32
Unk0 uint32
Operation uint32
PresentCount uint32
Unk3 uint32
Unk4 uint32
Unk5 uint32
Unk6 uint32
PresentType []uint32
}
// Opcode returns the ID associated with this packet type.
@@ -30,14 +31,19 @@ func (m *MsgMhfPresentBox) Opcode() network.PacketID {
func (m *MsgMhfPresentBox) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
m.Unk2 = bf.ReadUint32()
m.Operation = bf.ReadUint32()
m.PresentCount = bf.ReadUint32()
m.Unk3 = bf.ReadUint32()
m.Unk4 = bf.ReadUint32()
m.Unk5 = bf.ReadUint32()
m.Unk6 = bf.ReadUint32()
for i := uint32(0); i < m.Unk2; i++ {
m.Unk7 = append(m.Unk7, bf.ReadUint32())
for i := uint32(0); i < m.PresentCount; i++ {
m.PresentType = append(m.PresentType, bf.ReadUint32())
}
fmt.Printf("MsgMhfPresentBox: Unk0:[%d] Unk1:[%d] Unk2:[%d] Unk3:[%d] Unk4:[%d] Unk5:[%d] Unk6:[%d] \n\n", m.Unk0, m.Operation, m.PresentCount, m.Unk3, m.Unk4, m.Unk5, m.Unk6)
for _, mdata := range m.PresentType {
fmt.Printf("MsgMhfPresentBox: PresentType: [%d] \n", mdata)
}
return nil
}

View File

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

View File

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

View File

@@ -32,7 +32,9 @@ func (m *MsgMhfStampcardStamp) Opcode() network.PacketID {
func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.HR = bf.ReadUint16()
m.GR = bf.ReadUint16()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
m.GR = bf.ReadUint16()
}
m.Stamps = bf.ReadUint16()
bf.ReadUint16() // Zeroed
if _config.ErupeConfig.RealClientMode > _config.Z1 {

View File

@@ -9,7 +9,7 @@ import (
type Goocoo struct {
Index uint32
Data1 []uint16
Data1 []int16
Data2 []uint32
Name []byte
}
@@ -31,11 +31,11 @@ func (m *MsgMhfUpdateGuacot) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien
m.AckHandle = bf.ReadUint32()
m.EntryCount = bf.ReadUint16()
bf.ReadUint16() // Zeroed
var temp Goocoo
for i := 0; i < int(m.EntryCount); i++ {
var temp Goocoo
temp.Index = bf.ReadUint32()
for j := 0; j < 22; j++ {
temp.Data1 = append(temp.Data1, bf.ReadUint16())
temp.Data1 = append(temp.Data1, bf.ReadInt16())
}
for j := 0; j < 2; j++ {
temp.Data2 = append(temp.Data2, bf.ReadUint32())

View File

@@ -2,24 +2,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/mhfitem"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
type Item struct {
Unk0 uint32
ItemID uint16
Amount uint16
Unk1 uint32
}
// MsgMhfUpdateGuildItem represents the MSG_MHF_UPDATE_GUILD_ITEM
type MsgMhfUpdateGuildItem struct {
AckHandle uint32
GuildID uint32
Items []Item
AckHandle uint32
GuildID uint32
UpdatedItems []mhfitem.MHFItemStack
}
// Opcode returns the ID associated with this packet type.
@@ -31,18 +25,12 @@ func (m *MsgMhfUpdateGuildItem) Opcode() network.PacketID {
func (m *MsgMhfUpdateGuildItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.GuildID = bf.ReadUint32()
itemCount := int(bf.ReadUint16())
changes := int(bf.ReadUint16())
bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed
m.Items = make([]Item, itemCount)
for i := 0; i < itemCount; i++ {
m.Items[i].Unk0 = bf.ReadUint32()
m.Items[i].ItemID = bf.ReadUint16()
m.Items[i].Amount = bf.ReadUint16()
m.Items[i].Unk1 = bf.ReadUint32()
for i := 0; i < changes; i++ {
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
}
return nil
}

View File

@@ -2,6 +2,7 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/mhfitem"
"erupe-ce/common/byteframe"
"erupe-ce/network"
@@ -10,8 +11,8 @@ import (
// MsgMhfUpdateUnionItem represents the MSG_MHF_UPDATE_UNION_ITEM
type MsgMhfUpdateUnionItem struct {
AckHandle uint32
Items []Item
AckHandle uint32
UpdatedItems []mhfitem.MHFItemStack
}
// Opcode returns the ID associated with this packet type.
@@ -22,18 +23,12 @@ func (m *MsgMhfUpdateUnionItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfUpdateUnionItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
itemCount := int(bf.ReadUint16())
changes := int(bf.ReadUint16())
bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed
m.Items = make([]Item, itemCount)
for i := 0; i < itemCount; i++ {
m.Items[i].Unk0 = bf.ReadUint32()
m.Items[i].ItemID = bf.ReadUint16()
m.Items[i].Amount = bf.ReadUint16()
m.Items[i].Unk1 = bf.ReadUint32()
for i := 0; i < changes; i++ {
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
}
return nil
}

View File

@@ -3,25 +3,18 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfitem"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
type WarehouseStack struct {
ID uint32
Index uint16
EquipType uint16
ItemID uint16
Quantity uint16
Data []byte
}
// MsgMhfUpdateWarehouse represents the MSG_MHF_UPDATE_WAREHOUSE
type MsgMhfUpdateWarehouse struct {
AckHandle uint32
BoxType string
BoxIndex uint8
Updates []WarehouseStack
AckHandle uint32
BoxType uint8
BoxIndex uint8
UpdatedItems []mhfitem.MHFItemStack
UpdatedEquipment []mhfitem.MHFEquipment
}
// Opcode returns the ID associated with this packet type.
@@ -32,35 +25,19 @@ func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
boxType := bf.ReadUint8()
switch boxType {
case 0:
m.BoxType = "item"
case 1:
m.BoxType = "equip"
}
m.BoxType = bf.ReadUint8()
m.BoxIndex = bf.ReadUint8()
changes := int(bf.ReadUint16())
var stackUpdate WarehouseStack
bf.ReadUint8() // Zeroed
bf.ReadUint8() // Zeroed
for i := 0; i < changes; i++ {
switch boxType {
switch m.BoxType {
case 0:
stackUpdate.ID = bf.ReadUint32()
stackUpdate.Index = bf.ReadUint16()
stackUpdate.ItemID = bf.ReadUint16()
stackUpdate.Quantity = bf.ReadUint16()
_ = bf.ReadUint16() // Unk
m.Updates = append(m.Updates, stackUpdate)
m.UpdatedItems = append(m.UpdatedItems, mhfitem.ReadWarehouseItem(bf))
case 1:
stackUpdate.ID = bf.ReadUint32()
stackUpdate.Index = bf.ReadUint16()
stackUpdate.EquipType = bf.ReadUint16()
stackUpdate.ItemID = bf.ReadUint16()
stackUpdate.Data = bf.ReadBytes(56)
m.Updates = append(m.Updates, stackUpdate)
m.UpdatedEquipment = append(m.UpdatedEquipment, mhfitem.ReadWarehouseEquipment(bf))
}
}
_ = bf.ReadUint16()
return nil
}

View File

@@ -17,7 +17,7 @@ type TerminalLogEntry struct {
Unk0 int16
Unk1 int32
Unk2 int32
Unk3 int32
CID int32
Unk4 []int32
}
@@ -40,15 +40,15 @@ func (m *MsgSysTerminalLog) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
entryCount := int(bf.ReadUint16())
bf.ReadUint16() // Zeroed
var e TerminalLogEntry
for i := 0; i < entryCount; i++ {
var e TerminalLogEntry
e.Index = bf.ReadUint32()
e.Type1 = bf.ReadUint8()
e.Type2 = bf.ReadUint8()
e.Unk0 = bf.ReadInt16()
e.Unk1 = bf.ReadInt32()
e.Unk2 = bf.ReadInt32()
e.Unk3 = bf.ReadInt32()
e.CID = bf.ReadInt32()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
for j := 0; j < 4; j++ {
e.Unk4 = append(e.Unk4, bf.ReadInt32())

View File

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

BIN
rsrc_windows_amd64.syso Normal file

Binary file not shown.

View File

@@ -0,0 +1,754 @@
BEGIN;
INSERT INTO public.paper_data_gifts (gift_id, item_id, unk0,unk1,unk2) VALUES
(6001,11159, 1, 1, 5000),
(6001,11160, 1, 1, 3350),
(6001,11161, 1, 1, 1500),
(6001,11162, 1, 1, 100),
(6001,11163, 1, 1, 50),
(6002,11159, 2, 1, 1800),
(6002,11160, 2, 1, 1200),
(6002,11161, 2, 1, 500),
(6002,11162, 1, 1, 50),
(6002,11037, 1, 1, 150),
(6002,11038, 1, 1, 150),
(6002,11044, 1, 1, 150),
(6002,11057, 1, 1, 150),
(6002,11059, 1, 1, 150),
(6002,11079, 1, 1, 150),
(6002,11098, 1, 1, 150),
(6002,11104, 1, 1, 150),
(6002,11117, 1, 1, 150),
(6002,11128, 1, 1, 150),
(6002,11133, 1, 1, 150),
(6002,11137, 1, 1, 150),
(6002,11143, 1, 1, 150),
(6002,11132, 1, 1, 150),
(6002,11039, 1, 1, 150),
(6002,11040, 1, 1, 150),
(6002,11049, 1, 1, 150),
(6002,11061, 1, 1, 150),
(6002,11063, 1, 1, 150),
(6002,11077, 1, 1, 150),
(6002,11099, 1, 1, 150),
(6002,11105, 1, 1, 150),
(6002,11129, 1, 1, 150),
(6002,11130, 1, 1, 150),
(6002,11131, 1, 1, 150),
(6002,11139, 1, 1, 150),
(6002,11145, 1, 1, 150),
(6002,11096, 1, 1, 150),
(6002,11041, 1, 1, 150),
(6002,11047, 1, 1, 150),
(6002,11054, 1, 1, 150),
(6002,11065, 1, 1, 150),
(6002,11068, 1, 1, 150),
(6002,11075, 1, 1, 150),
(6002,11100, 1, 1, 150),
(6002,11106, 1, 1, 150),
(6002,11119, 1, 1, 150),
(6002,11135, 1, 1, 150),
(6002,11136, 1, 1, 150),
(6002,11138, 1, 1, 150),
(6002,11088, 1, 1, 150),
(6002,10370, 1, 1, 150),
(6002,10368, 1, 1, 150),
(6010,11159, 1, 1, 3700),
(6010,11160, 1, 1, 2900),
(6010,11161, 1, 1, 1300),
(6010,11453, 1, 1, 250),
(6010,11454, 1, 1, 250),
(6010,12055, 1, 1, 250),
(6010,12065, 1, 1, 250),
(6010,12058, 1, 1, 250),
(6010,12068, 1, 1, 250),
(6010,11774, 1, 1, 200),
(6010,11773, 1, 1, 400),
(6011,11159, 1, 1, 3700),
(6011,11160, 1, 1, 2900),
(6011,11161, 1, 1, 1300),
(6011,11453, 1, 1, 250),
(6011,11454, 1, 1, 250),
(6011,12055, 1, 1, 250),
(6011,12065, 1, 1, 250),
(6011,12058, 1, 1, 250),
(6011,12068, 1, 1, 250),
(6011,11774, 1, 1, 200),
(6011,11773, 1, 1, 400),
(6012,11159, 2, 1, 3500),
(6012,11160, 2, 1, 2900),
(6012,11161, 2, 1, 1300),
(6012,12508, 1, 1, 400),
(6012,11453, 1, 1, 200),
(6012,11454, 1, 1, 200),
(6012,12055, 1, 1, 200),
(6012,12065, 1, 1, 200),
(6012,12058, 1, 1, 200),
(6012,12068, 1, 1, 200),
(6012,11775, 1, 1, 400),
(6012,11776, 1, 1, 200),
(6012,11777, 1, 1, 100),
(7001,11037, 1, 1, 290),
(7001,11038, 1, 1, 270),
(7001,11044, 1, 1, 270),
(7001,11057, 1, 1, 290),
(7001,11059, 1, 1, 290),
(7001,11079, 1, 1, 290),
(7001,11098, 1, 1, 280),
(7001,11104, 1, 1, 300),
(7001,11117, 1, 1, 280),
(7001,11128, 1, 1, 290),
(7001,11133, 1, 1, 290),
(7001,11137, 1, 1, 300),
(7001,11143, 1, 1, 290),
(7001,11132, 1, 1, 270),
(7001,11042, 1, 1, 47),
(7001,11045, 1, 1, 47),
(7001,11064, 1, 1, 47),
(7001,11062, 1, 1, 47),
(7001,11070, 1, 1, 48),
(7001,11101, 1, 1, 47),
(7001,11108, 1, 1, 47),
(7001,11109, 1, 1, 47),
(7001,11120, 1, 1, 47),
(7001,11122, 1, 1, 47),
(7001,11134, 1, 1, 47),
(7001,11141, 1, 1, 47),
(7001,11084, 1, 1, 47),
(7001,11087, 1, 1, 47),
(7001,11094, 1, 1, 47),
(7001,10374, 1, 1, 47),
(7001,10375, 1, 1, 47),
(7001,11051, 1, 1, 17),
(7001,11071, 1, 1, 16),
(7001,11076, 1, 1, 16),
(7001,11102, 1, 1, 17),
(7001,11124, 1, 1, 17),
(7001,11090, 1, 1, 17),
(7001,11159, 1, 1, 1200),
(7001,11159, 2, 1, 650),
(7001,11160, 1, 1, 800),
(7001,11160, 2, 1, 300),
(7001,11161, 1, 1, 100),
(7001,11161, 2, 1, 50),
(7001,11164, 1, 1, 100),
(7001,11162, 1, 1, 100),
(7001,11163, 1, 1, 100),
(7001,11158, 1, 1, 300),
(7001,11463, 1, 1, 300),
(7001,11356, 1, 1, 300),
(7001,11464, 1, 1, 300),
(7001,11357, 1, 1, 500),
(7001,11039, 1, 2, 300),
(7001,11040, 1, 2, 270),
(7001,11049, 1, 2, 300),
(7001,11061, 1, 2, 290),
(7001,11063, 1, 2, 290),
(7001,11077, 1, 2, 290),
(7001,11099, 1, 2, 280),
(7001,11105, 1, 2, 300),
(7001,11129, 1, 2, 250),
(7001,11130, 1, 2, 300),
(7001,11131, 1, 2, 280),
(7001,11139, 1, 2, 290),
(7001,11145, 1, 2, 260),
(7001,11096, 1, 2, 300),
(7001,11046, 1, 2, 47),
(7001,11066, 1, 2, 47),
(7001,11067, 1, 2, 47),
(7001,11072, 1, 2, 47),
(7001,11082, 1, 2, 47),
(7001,11103, 1, 2, 47),
(7001,11110, 1, 2, 47),
(7001,11112, 1, 2, 47),
(7001,11114, 1, 2, 47),
(7001,11115, 1, 2, 47),
(7001,11121, 1, 2, 47),
(7001,11144, 1, 2, 48),
(7001,11085, 1, 2, 47),
(7001,11089, 1, 2, 47),
(7001,11091, 1, 2, 47),
(7001,10376, 1, 2, 47),
(7001,10377, 1, 2, 47),
(7001,11127, 1, 2, 17),
(7001,11069, 1, 2, 17),
(7001,11142, 1, 2, 17),
(7001,11078, 1, 2, 17),
(7001,11056, 1, 2, 16),
(7001,11092, 1, 2, 16),
(7001,11159, 1, 2, 1200),
(7001,11159, 2, 2, 650),
(7001,11160, 1, 2, 800),
(7001,11160, 2, 2, 300),
(7001,11161, 1, 2, 100),
(7001,11161, 2, 2, 50),
(7001,11164, 1, 2, 100),
(7001,11162, 1, 2, 100),
(7001,11163, 1, 2, 100),
(7001,11158, 1, 2, 300),
(7001,11463, 1, 2, 300),
(7001,11356, 1, 2, 300),
(7001,11464, 1, 2, 300),
(7001,11357, 1, 2, 500),
(7001,11041, 1, 3, 266),
(7001,11047, 1, 3, 266),
(7001,11054, 1, 3, 266),
(7001,11065, 1, 3, 266),
(7001,11068, 1, 3, 266),
(7001,11075, 1, 3, 266),
(7001,11100, 1, 3, 266),
(7001,11106, 1, 3, 266),
(7001,11119, 1, 3, 266),
(7001,11135, 1, 3, 268),
(7001,11136, 1, 3, 268),
(7001,11138, 1, 3, 268),
(7001,11088, 1, 3, 268),
(7001,10370, 1, 3, 266),
(7001,10368, 1, 3, 268),
(7001,11043, 1, 3, 50),
(7001,11048, 1, 3, 50),
(7001,11050, 1, 3, 50),
(7001,11058, 1, 3, 50),
(7001,11060, 1, 3, 50),
(7001,11074, 1, 3, 50),
(7001,11107, 1, 3, 50),
(7001,11111, 1, 3, 50),
(7001,11113, 1, 3, 50),
(7001,11118, 1, 3, 50),
(7001,11126, 1, 3, 50),
(7001,11140, 1, 3, 50),
(7001,11086, 1, 3, 50),
(7001,11095, 1, 3, 50),
(7001,11055, 1, 3, 50),
(7001,10378, 1, 3, 50),
(7001,11052, 1, 3, 15),
(7001,11073, 1, 3, 15),
(7001,11146, 1, 3, 15),
(7001,11116, 1, 3, 15),
(7001,11123, 1, 3, 15),
(7001,11097, 1, 3, 15),
(7001,10367, 1, 3, 15),
(7001,10371, 1, 3, 15),
(7001,10373, 1, 3, 15),
(7001,10778, 1, 3, 375),
(7001,11209, 1, 3, 375),
(7001,10813, 1, 3, 375),
(7001,11389, 1, 3, 375),
(7001,11159, 1, 3, 1000),
(7001,11159, 2, 3, 250),
(7001,11160, 1, 3, 700),
(7001,11160, 2, 3, 175),
(7001,11161, 1, 3, 300),
(7001,11161, 2, 3, 75),
(7001,11465, 1, 3, 53),
(7001,11466, 1, 3, 27),
(7001,11467, 1, 3, 266),
(7001,11468, 1, 3, 533),
(7001,11469, 1, 3, 186),
(7002,11037, 1, 1, 100),
(7002,11038, 1, 1, 100),
(7002,11044, 1, 1, 100),
(7002,11057, 1, 1, 100),
(7002,11059, 1, 1, 100),
(7002,11079, 1, 1, 100),
(7002,11098, 1, 1, 100),
(7002,11104, 1, 1, 100),
(7002,11117, 1, 1, 100),
(7002,11128, 1, 1, 100),
(7002,11133, 1, 1, 100),
(7002,11137, 1, 1, 100),
(7002,11143, 1, 1, 100),
(7002,11132, 1, 1, 100),
(7002,11042, 1, 1, 60),
(7002,11045, 1, 1, 60),
(7002,11064, 1, 1, 60),
(7002,11062, 1, 1, 60),
(7002,11070, 1, 1, 60),
(7002,11101, 1, 1, 60),
(7002,11108, 1, 1, 60),
(7002,11109, 1, 1, 60),
(7002,11120, 1, 1, 60),
(7002,11122, 1, 1, 60),
(7002,11134, 1, 1, 60),
(7002,11141, 1, 1, 60),
(7002,11084, 1, 1, 60),
(7002,11087, 1, 1, 60),
(7002,11094, 1, 1, 60),
(7002,10374, 1, 1, 60),
(7002,10375, 1, 1, 60),
(7002,11051, 1, 1, 20),
(7002,11071, 1, 1, 20),
(7002,11076, 1, 1, 20),
(7002,11102, 1, 1, 20),
(7002,11124, 1, 1, 20),
(7002,11090, 1, 1, 20),
(7002,11164, 1, 1, 400),
(7002,11162, 1, 1, 200),
(7002,11163, 1, 1, 200),
(7002,11463, 1, 1, 100),
(7002,11464, 1, 1, 150),
(7002,10355, 1, 1, 150),
(7002,12506, 1, 1, 200),
(7002,12507, 1, 1, 300),
(7002,12508, 1, 1, 900),
(7002,13629, 1, 1, 350),
(7002,13628, 1, 1, 200),
(7002,11356, 1, 1, 100),
(7002,11357, 1, 1, 150),
(7002,12014, 1, 1, 250),
(7002,12016, 1, 1, 400),
(7002,12015, 1, 1, 410),
(7002,11159, 2, 1, 500),
(7002,11159, 4, 1, 500),
(7002,11159, 6, 1, 500),
(7002,11160, 2, 1, 400),
(7002,11160, 4, 1, 400),
(7002,11160, 6, 1, 400),
(7002,11161, 2, 1, 100),
(7002,11161, 4, 1, 100),
(7002,11161, 6, 1, 100),
(7002,11039, 1, 2, 100),
(7002,11040, 1, 2, 100),
(7002,11049, 1, 2, 100),
(7002,11061, 1, 2, 100),
(7002,11063, 1, 2, 100),
(7002,11077, 1, 2, 100),
(7002,11099, 1, 2, 100),
(7002,11105, 1, 2, 100),
(7002,11129, 1, 2, 100),
(7002,11130, 1, 2, 100),
(7002,11131, 1, 2, 100),
(7002,11139, 1, 2, 100),
(7002,11145, 1, 2, 100),
(7002,11096, 1, 2, 100),
(7002,11046, 1, 2, 60),
(7002,11066, 1, 2, 60),
(7002,11067, 1, 2, 60),
(7002,11072, 1, 2, 60),
(7002,11082, 1, 2, 60),
(7002,11103, 1, 2, 60),
(7002,11110, 1, 2, 60),
(7002,11112, 1, 2, 60),
(7002,11114, 1, 2, 60),
(7002,11115, 1, 2, 60),
(7002,11121, 1, 2, 60),
(7002,11144, 1, 2, 60),
(7002,11085, 1, 2, 60),
(7002,11089, 1, 2, 60),
(7002,11091, 1, 2, 60),
(7002,10376, 1, 2, 60),
(7002,10377, 1, 2, 60),
(7002,11127, 1, 2, 20),
(7002,11069, 1, 2, 20),
(7002,11142, 1, 2, 20),
(7002,11078, 1, 2, 20),
(7002,11056, 1, 2, 20),
(7002,11092, 1, 2, 20),
(7002,11164, 1, 2, 400),
(7002,11162, 1, 2, 200),
(7002,11163, 1, 2, 200),
(7002,11463, 1, 2, 250),
(7002,11464, 1, 2, 350),
(7002,12506, 1, 2, 150),
(7002,12507, 1, 2, 200),
(7002,12508, 1, 2, 350),
(7002,13629, 1, 2, 250),
(7002,13628, 1, 2, 200),
(7002,10355, 1, 2, 400),
(7002,11158, 1, 2, 100),
(7002,11356, 1, 2, 100),
(7002,11357, 1, 2, 100),
(7002,12014, 1, 2, 300),
(7002,12016, 1, 2, 450),
(7002,12015, 1, 2, 460),
(7002,11159, 2, 2, 500),
(7002,11159, 4, 2, 500),
(7002,11159, 6, 2, 500),
(7002,11160, 2, 2, 400),
(7002,11160, 4, 2, 400),
(7002,11160, 6, 2, 400),
(7002,11161, 2, 2, 100),
(7002,11161, 4, 2, 100),
(7002,11161, 6, 2, 100),
(7002,11041, 1, 3, 120),
(7002,11047, 1, 3, 120),
(7002,11054, 1, 3, 120),
(7002,11065, 1, 3, 120),
(7002,11068, 1, 3, 120),
(7002,11075, 1, 3, 120),
(7002,11100, 1, 3, 120),
(7002,11106, 1, 3, 120),
(7002,11119, 1, 3, 120),
(7002,11135, 1, 3, 120),
(7002,11136, 1, 3, 120),
(7002,11138, 1, 3, 120),
(7002,11088, 1, 3, 120),
(7002,10370, 1, 3, 120),
(7002,10368, 1, 3, 120),
(7002,11043, 1, 3, 65),
(7002,11048, 1, 3, 65),
(7002,11050, 1, 3, 65),
(7002,11058, 1, 3, 65),
(7002,11060, 1, 3, 65),
(7002,11074, 1, 3, 65),
(7002,11107, 1, 3, 65),
(7002,11111, 1, 3, 65),
(7002,11113, 1, 3, 65),
(7002,11118, 1, 3, 65),
(7002,11126, 1, 3, 65),
(7002,11140, 1, 3, 65),
(7002,11086, 1, 3, 65),
(7002,11095, 1, 3, 65),
(7002,11055, 1, 3, 65),
(7002,10378, 1, 3, 65),
(7002,11052, 1, 3, 15),
(7002,11073, 1, 3, 15),
(7002,11146, 1, 3, 15),
(7002,11116, 1, 3, 15),
(7002,11123, 1, 3, 15),
(7002,11097, 1, 3, 15),
(7002,10367, 1, 3, 15),
(7002,10371, 1, 3, 15),
(7002,10373, 1, 3, 15),
(7002,10778, 3, 3, 490),
(7002,11209, 3, 3, 490),
(7002,10813, 3, 3, 490),
(7002,11389, 3, 3, 490),
(7002,12046, 3, 3, 500),
(7002,12503, 3, 3, 500),
(7002,11159, 2, 3, 500),
(7002,11159, 4, 3, 500),
(7002,11159, 6, 3, 500),
(7002,11160, 2, 3, 400),
(7002,11160, 4, 3, 400),
(7002,11160, 6, 3, 400),
(7002,11161, 2, 3, 100),
(7002,11161, 4, 3, 100),
(7002,11161, 6, 3, 100),
(7002,11465, 1, 3, 53),
(7002,11466, 1, 3, 27),
(7002,11467, 1, 3, 266),
(7002,11468, 1, 3, 533),
(7002,11469, 1, 3, 186),
(7011,11037, 1, 1, 290),
(7011,11038, 1, 1, 270),
(7011,11044, 1, 1, 270),
(7011,11057, 1, 1, 290),
(7011,11059, 1, 1, 290),
(7011,11079, 1, 1, 290),
(7011,11098, 1, 1, 280),
(7011,11104, 1, 1, 300),
(7011,11117, 1, 1, 280),
(7011,11128, 1, 1, 290),
(7011,11133, 1, 1, 290),
(7011,11137, 1, 1, 300),
(7011,11143, 1, 1, 290),
(7011,11132, 1, 1, 270),
(7011,11042, 1, 1, 47),
(7011,11045, 1, 1, 47),
(7011,11064, 1, 1, 47),
(7011,11062, 1, 1, 47),
(7011,11070, 1, 1, 48),
(7011,11101, 1, 1, 47),
(7011,11108, 1, 1, 47),
(7011,11109, 1, 1, 47),
(7011,11120, 1, 1, 47),
(7011,11122, 1, 1, 47),
(7011,11134, 1, 1, 47),
(7011,11141, 1, 1, 47),
(7011,11084, 1, 1, 47),
(7011,11087, 1, 1, 47),
(7011,11094, 1, 1, 47),
(7011,10374, 1, 1, 47),
(7011,10375, 1, 1, 47),
(7011,11051, 1, 1, 17),
(7011,11071, 1, 1, 16),
(7011,11076, 1, 1, 16),
(7011,11102, 1, 1, 17),
(7011,11124, 1, 1, 17),
(7011,11090, 1, 1, 17),
(7011,11159, 1, 1, 1200),
(7011,11159, 2, 1, 650),
(7011,11160, 1, 1, 800),
(7011,11160, 2, 1, 300),
(7011,11161, 1, 1, 100),
(7011,11161, 2, 1, 50),
(7011,11164, 1, 1, 100),
(7011,11162, 1, 1, 100),
(7011,11163, 1, 1, 100),
(7011,11158, 1, 1, 300),
(7011,11463, 1, 1, 300),
(7011,11356, 1, 1, 300),
(7011,11464, 1, 1, 300),
(7011,11357, 1, 1, 500),
(7011,11039, 1, 2, 300),
(7011,11040, 1, 2, 270),
(7011,11049, 1, 2, 300),
(7011,11061, 1, 2, 290),
(7011,11063, 1, 2, 290),
(7011,11077, 1, 2, 290),
(7011,11099, 1, 2, 280),
(7011,11105, 1, 2, 300),
(7011,11129, 1, 2, 250),
(7011,11130, 1, 2, 300),
(7011,11131, 1, 2, 280),
(7011,11139, 1, 2, 290),
(7011,11145, 1, 2, 260),
(7011,11096, 1, 2, 300),
(7011,11046, 1, 2, 47),
(7011,11066, 1, 2, 47),
(7011,11067, 1, 2, 47),
(7011,11072, 1, 2, 47),
(7011,11082, 1, 2, 47),
(7011,11103, 1, 2, 47),
(7011,11110, 1, 2, 47),
(7011,11112, 1, 2, 47),
(7011,11114, 1, 2, 47),
(7011,11115, 1, 2, 47),
(7011,11121, 1, 2, 47),
(7011,11144, 1, 2, 48),
(7011,11085, 1, 2, 47),
(7011,11089, 1, 2, 47),
(7011,11091, 1, 2, 47),
(7011,10376, 1, 2, 47),
(7011,10377, 1, 2, 47),
(7011,11127, 1, 2, 17),
(7011,11069, 1, 2, 17),
(7011,11142, 1, 2, 17),
(7011,11078, 1, 2, 17),
(7011,11056, 1, 2, 16),
(7011,11092, 1, 2, 16),
(7011,11159, 1, 2, 1200),
(7011,11159, 2, 2, 650),
(7011,11160, 1, 2, 800),
(7011,11160, 2, 2, 300),
(7011,11161, 1, 2, 100),
(7011,11161, 2, 2, 50),
(7011,11164, 1, 2, 100),
(7011,11162, 1, 2, 100),
(7011,11163, 1, 2, 100),
(7011,11158, 1, 2, 300),
(7011,11463, 1, 2, 300),
(7011,11356, 1, 2, 300),
(7011,11464, 1, 2, 300),
(7011,11357, 1, 2, 500),
(7011,11041, 1, 3, 266),
(7011,11047, 1, 3, 266),
(7011,11054, 1, 3, 266),
(7011,11065, 1, 3, 266),
(7011,11068, 1, 3, 266),
(7011,11075, 1, 3, 266),
(7011,11100, 1, 3, 266),
(7011,11106, 1, 3, 266),
(7011,11119, 1, 3, 266),
(7011,11135, 1, 3, 268),
(7011,11136, 1, 3, 268),
(7011,11138, 1, 3, 268),
(7011,11088, 1, 3, 268),
(7011,10370, 1, 3, 266),
(7011,10368, 1, 3, 268),
(7011,11043, 1, 3, 50),
(7011,11048, 1, 3, 50),
(7011,11050, 1, 3, 50),
(7011,11058, 1, 3, 50),
(7011,11060, 1, 3, 50),
(7011,11074, 1, 3, 50),
(7011,11107, 1, 3, 50),
(7011,11111, 1, 3, 50),
(7011,11113, 1, 3, 50),
(7011,11118, 1, 3, 50),
(7011,11126, 1, 3, 50),
(7011,11140, 1, 3, 50),
(7011,11086, 1, 3, 50),
(7011,11095, 1, 3, 50),
(7011,11055, 1, 3, 50),
(7011,10378, 1, 3, 50),
(7011,11052, 1, 3, 15),
(7011,11073, 1, 3, 15),
(7011,11146, 1, 3, 15),
(7011,11116, 1, 3, 15),
(7011,11123, 1, 3, 15),
(7011,11097, 1, 3, 15),
(7011,10367, 1, 3, 15),
(7011,10371, 1, 3, 15),
(7011,10373, 1, 3, 15),
(7011,10778, 1, 3, 375),
(7011,11209, 1, 3, 375),
(7011,10813, 1, 3, 375),
(7011,11389, 1, 3, 375),
(7011,11159, 1, 3, 1000),
(7011,11159, 2, 3, 250),
(7011,11160, 1, 3, 700),
(7011,11160, 2, 3, 175),
(7011,11161, 1, 3, 300),
(7011,11161, 2, 3, 75),
(7011,11465, 1, 3, 53),
(7011,11466, 1, 3, 27),
(7011,11467, 1, 3, 266),
(7011,11468, 1, 3, 533),
(7011,11469, 1, 3, 186),
(7012,11037, 1, 1, 290),
(7012,11038, 1, 1, 270),
(7012,11044, 1, 1, 270),
(7012,11057, 1, 1, 290),
(7012,11059, 1, 1, 290),
(7012,11079, 1, 1, 290),
(7012,11098, 1, 1, 280),
(7012,11104, 1, 1, 300),
(7012,11117, 1, 1, 280),
(7012,11128, 1, 1, 290),
(7012,11133, 1, 1, 290),
(7012,11137, 1, 1, 300),
(7012,11143, 1, 1, 290),
(7012,11132, 1, 1, 270),
(7012,11042, 1, 1, 47),
(7012,11045, 1, 1, 47),
(7012,11064, 1, 1, 47),
(7012,11062, 1, 1, 47),
(7012,11070, 1, 1, 48),
(7012,11101, 1, 1, 47),
(7012,11108, 1, 1, 47),
(7012,11109, 1, 1, 47),
(7012,11120, 1, 1, 47),
(7012,11122, 1, 1, 47),
(7012,11134, 1, 1, 47),
(7012,11141, 1, 1, 47),
(7012,11084, 1, 1, 47),
(7012,11087, 1, 1, 47),
(7012,11094, 1, 1, 47),
(7012,10374, 1, 1, 47),
(7012,10375, 1, 1, 47),
(7012,11051, 1, 1, 17),
(7012,11071, 1, 1, 16),
(7012,11076, 1, 1, 16),
(7012,11102, 1, 1, 17),
(7012,11124, 1, 1, 17),
(7012,11090, 1, 1, 17),
(7012,11159, 1, 1, 1200),
(7012,11159, 2, 1, 650),
(7012,11160, 1, 1, 800),
(7012,11160, 2, 1, 300),
(7012,11161, 1, 1, 100),
(7012,11161, 2, 1, 50),
(7012,11164, 1, 1, 100),
(7012,11162, 1, 1, 100),
(7012,11163, 1, 1, 100),
(7012,11158, 1, 1, 300),
(7012,11463, 1, 1, 300),
(7012,11356, 1, 1, 300),
(7012,11464, 1, 1, 300),
(7012,11357, 1, 1, 500),
(7012,11039, 1, 2, 300),
(7012,11040, 1, 2, 270),
(7012,11049, 1, 2, 300),
(7012,11061, 1, 2, 290),
(7012,11063, 1, 2, 290),
(7012,11077, 1, 2, 290),
(7012,11099, 1, 2, 280),
(7012,11105, 1, 2, 300),
(7012,11129, 1, 2, 250),
(7012,11130, 1, 2, 300),
(7012,11131, 1, 2, 280),
(7012,11139, 1, 2, 290),
(7012,11145, 1, 2, 260),
(7012,11096, 1, 2, 300),
(7012,11046, 1, 2, 47),
(7012,11066, 1, 2, 47),
(7012,11067, 1, 2, 47),
(7012,11072, 1, 2, 47),
(7012,11082, 1, 2, 47),
(7012,11103, 1, 2, 47),
(7012,11110, 1, 2, 47),
(7012,11112, 1, 2, 47),
(7012,11114, 1, 2, 47),
(7012,11115, 1, 2, 47),
(7012,11121, 1, 2, 47),
(7012,11144, 1, 2, 48),
(7012,11085, 1, 2, 47),
(7012,11089, 1, 2, 47),
(7012,11091, 1, 2, 47),
(7012,10376, 1, 2, 47),
(7012,10377, 1, 2, 47),
(7012,11127, 1, 2, 17),
(7012,11069, 1, 2, 17),
(7012,11142, 1, 2, 17),
(7012,11078, 1, 2, 17),
(7012,11056, 1, 2, 16),
(7012,11092, 1, 2, 16),
(7012,11159, 1, 2, 1200),
(7012,11159, 2, 2, 650),
(7012,11160, 1, 2, 800),
(7012,11160, 2, 2, 300),
(7012,11161, 1, 2, 100),
(7012,11161, 2, 2, 50),
(7012,11164, 1, 2, 100),
(7012,11162, 1, 2, 100),
(7012,11163, 1, 2, 100),
(7012,11158, 1, 2, 300),
(7012,11463, 1, 2, 300),
(7012,11356, 1, 2, 300),
(7012,11464, 1, 2, 300),
(7012,11357, 1, 2, 500),
(7012,11041, 1, 3, 266),
(7012,11047, 1, 3, 266),
(7012,11054, 1, 3, 266),
(7012,11065, 1, 3, 266),
(7012,11068, 1, 3, 266),
(7012,11075, 1, 3, 266),
(7012,11100, 1, 3, 266),
(7012,11106, 1, 3, 266),
(7012,11119, 1, 3, 266),
(7012,11135, 1, 3, 268),
(7012,11136, 1, 3, 268),
(7012,11138, 1, 3, 268),
(7012,11088, 1, 3, 268),
(7012,10370, 1, 3, 266),
(7012,10368, 1, 3, 268),
(7012,11043, 1, 3, 50),
(7012,11048, 1, 3, 50),
(7012,11050, 1, 3, 50),
(7012,11058, 1, 3, 50),
(7012,11060, 1, 3, 50),
(7012,11074, 1, 3, 50),
(7012,11107, 1, 3, 50),
(7012,11111, 1, 3, 50),
(7012,11113, 1, 3, 50),
(7012,11118, 1, 3, 50),
(7012,11126, 1, 3, 50),
(7012,11140, 1, 3, 50),
(7012,11086, 1, 3, 50),
(7012,11095, 1, 3, 50),
(7012,11055, 1, 3, 50),
(7012,10378, 1, 3, 50),
(7012,11052, 1, 3, 15),
(7012,11073, 1, 3, 15),
(7012,11146, 1, 3, 15),
(7012,11116, 1, 3, 15),
(7012,11123, 1, 3, 15),
(7012,11097, 1, 3, 15),
(7012,10367, 1, 3, 15),
(7012,10371, 1, 3, 15),
(7012,10373, 1, 3, 15),
(7012,10778, 1, 3, 375),
(7012,11209, 1, 3, 375),
(7012,10813, 1, 3, 375),
(7012,11389, 1, 3, 375),
(7012,11159, 1, 3, 1000),
(7012,11159, 2, 3, 250),
(7012,11160, 1, 3, 700),
(7012,11160, 2, 3, 175),
(7012,11161, 1, 3, 300),
(7012,11161, 2, 3, 75),
(7012,11465, 1, 3, 53),
(7012,11466, 1, 3, 27),
(7012,11467, 1, 3, 266),
(7012,11468, 1, 3, 533),
(7012,11469, 1, 3, 186);
END;

View File

@@ -0,0 +1,189 @@
BEGIN;
INSERT INTO public.weekly_seibatu_ranking_reward (reward_id, index0, index1, index2, distribution_type, item_id, amount) VALUES
-- Contribution Reward --
(260003,1, 0, 0, 7201, 11158, 1),
(260003,2, 0, 0, 7201, 11173, 1),
(260003,3, 0, 0, 7201, 10813, 3),
(260003,4, 0, 0, 7201, 11163, 1),
(260003,5, 0, 0, 7201, 11164, 1),
(260003,6, 0, 0, 7201, 11389, 3),
(260003,6, 0, 0, 7201, 11381, 1),
(260003,7, 0, 0, 7201, 11384, 1),
(260003,8, 0, 0, 7201, 11159, 10),
(260003,9, 0, 0, 7201, 11160, 10),
(260003,10, 0, 0, 7201, 11161, 10),
(260003,11, 0, 0, 7201, 11265, 2),
(260003,11, 0, 0, 7201, 7279, 2),
(260003,12, 0, 0, 7201, 11381, 1),
(260003,13, 0, 0, 7201, 11384, 1),
(260003,14, 0, 0, 7201, 11381, 1),
(260003,15, 0, 0, 7201, 11384, 1),
(260003,15, 0, 0, 7201, 11464, 1),
(260003,16, 0, 0, 7201, 11381, 1),
(260003,17, 0, 0, 7201, 11384, 1),
(260003,18, 0, 0, 7201, 11381, 1),
(260003,19, 0, 0, 7201, 11384, 1),
(260003,20, 0, 0, 7201, 10778, 3),
(260003,21, 0, 0, 7201, 11265, 2),
(260003,21, 0, 0, 7201, 7279, 2),
(260003,22, 0, 0, 7201, 11381, 1),
(260003,23, 0, 0, 7201, 11384, 1),
(260003,24, 0, 0, 7201, 11381, 1),
(260003,25, 0, 0, 7201, 11389, 3),
(260003,25, 0, 0, 7201, 11286, 4),
(260003,26, 0, 0, 7201, 11384, 1),
(260003,27, 0, 0, 7201, 11381, 1),
(260003,28, 0, 0, 7201, 11384, 1),
(260003,29, 0, 0, 7201, 11381, 1),
(260003,30, 0, 0, 7201, 11209, 3),
(260003,31, 0, 0, 7201, 11265, 2),
(260003,31, 0, 0, 7201, 7279, 2),
(260003,32, 0, 0, 7201, 11159, 10),
(260003,33, 0, 0, 7201, 11463, 1),
(260003,34, 0, 0, 7201, 11160, 10),
(260003,35, 0, 0, 7201, 11286, 4),
(260003,36, 0, 0, 7201, 11161, 10),
(260003,38, 0, 0, 7201, 11384, 1),
(260003,39, 0, 0, 7201, 11164, 1),
(260003,40, 0, 0, 7201, 10813, 3),
(260003,41, 0, 0, 7201, 11265, 2),
(260003,41, 0, 0, 7201, 7280, 2),
(260003,43, 0, 0, 7201, 11381, 1),
(260003,45, 0, 0, 7201, 11286, 4),
(260003,47, 0, 0, 7201, 11384, 1),
(260003,48, 0, 0, 7201, 11358, 1),
(260003,50, 0, 0, 7201, 11356, 1),
(260003,51, 0, 0, 7201, 11265, 2),
(260003,51, 0, 0, 7201, 7280, 2),
(260003,53, 0, 0, 7201, 11381, 2),
(260003,55, 0, 0, 7201, 11357, 1),
(260003,57, 0, 0, 7201, 11384, 1),
(260003,60, 0, 0, 7201, 11286, 4),
(260003,61, 0, 0, 7201, 11265, 2),
(260003,61, 0, 0, 7201, 7280, 2),
(260003,63, 0, 0, 7201, 11381, 2),
(260003,66, 0, 0, 7201, 11463, 1),
(260003,67, 0, 0, 7201, 11384, 1),
(260003,70, 0, 0, 7201, 11286, 4),
(260003,71, 0, 0, 7201, 11265, 2),
(260003,71, 0, 0, 7201, 7280, 2),
(260003,73, 0, 0, 7201, 11381, 2),
(260003,77, 0, 0, 7201, 11384, 1),
(260003,79, 0, 0, 7201, 11164, 1),
(260003,80, 0, 0, 7201, 11286, 6),
(260003,81, 0, 0, 7201, 11265, 2),
(260003,81, 0, 0, 7201, 7281, 1),
(260003,83, 0, 0, 7201, 11381, 2),
(260003,85, 0, 0, 7201, 11464, 1),
(260003,87, 0, 0, 7201, 11384, 1),
(260003,90, 0, 0, 7201, 11286, 6),
(260003,91, 0, 0, 7201, 11265, 2),
(260003,91, 0, 0, 7201, 7281, 1),
(260003,93, 0, 0, 7201, 11381, 2),
(260003,95, 0, 0, 7201, 10778, 3),
(260003,97, 0, 0, 7201, 11384, 1),
(260003,99, 0, 0, 7201, 11463, 1),
(260003,100, 0, 0, 7201, 11286, 6),
(260003,101, 0, 0, 7201, 11265, 2),
(260003,101, 0, 0, 7201, 7281, 1),
(260003,103, 0, 0, 7201, 11381, 2),
(260003,107, 0, 0, 7201, 11384, 1),
(260003,110, 0, 0, 7201, 11286, 6),
(260003,113, 0, 0, 7201, 11381, 2),
(260003,115, 0, 0, 7201, 11164, 1),
(260003,117, 0, 0, 7201, 11384, 1),
(260003,120, 0, 0, 7201, 11286, 12),
(260003,123, 0, 0, 7201, 11381, 2),
(260003,127, 0, 0, 7201, 11384, 1),
(260003,130, 0, 0, 7201, 11286, 12),
(260003,132, 0, 0, 7201, 11381, 2),
(260003,134, 0, 0, 7201, 11384, 1),
(260003,136, 0, 0, 7201, 11381, 2),
(260003,138, 0, 0, 7201, 11384, 1),
(260003,140, 0, 0, 7201, 11286, 12),
(260003,142, 0, 0, 7201, 11382, 1),
(260003,144, 0, 0, 7201, 11385, 1),
(260003,145, 0, 0, 7201, 11464, 1),
(260003,146, 0, 0, 7201, 11382, 1),
(260003,148, 0, 0, 7201, 11385, 1),
(260003,150, 0, 0, 7201, 11164, 1),
(260003,155, 0, 0, 7201, 11382, 1),
(260003,160, 0, 0, 7201, 11209, 3),
(260003,165, 0, 0, 7201, 11385, 1),
(260003,170, 0, 0, 7201, 11159, 10),
(260003,175, 0, 0, 7201, 11382, 1),
(260003,180, 0, 0, 7201, 11160, 10),
(260003,185, 0, 0, 7201, 11385, 1),
(260003,190, 0, 0, 7201, 11161, 10),
(260003,195, 0, 0, 7201, 11382, 1),
(260003,200, 0, 0, 7201, 11159, 15),
(260003,210, 0, 0, 7201, 11160, 15),
(260003,220, 0, 0, 7201, 11385, 1),
(260003,235, 0, 0, 7201, 11382, 2),
(260003,250, 0, 0, 7201, 11161, 15),
(260003,265, 0, 0, 7201, 11159, 20),
(260003,280, 0, 0, 7201, 11385, 1),
(260003,300, 0, 0, 7201, 11160, 20),
(260003,315, 0, 0, 7201, 11382, 2),
(260003,330, 0, 0, 7201, 11385, 1),
(260003,350, 0, 0, 7201, 11161, 20),
(260003,365, 0, 0, 7201, 11382, 2),
(260003,380, 0, 0, 7201, 11385, 1),
(260003,400, 0, 0, 7201, 11159, 25),
(260003,415, 0, 0, 7201, 11382, 2),
(260003,430, 0, 0, 7201, 11385, 1),
(260003,450, 0, 0, 7201, 11160, 25),
(260003,465, 0, 0, 7201, 11382, 2),
(260003,480, 0, 0, 7201, 11385, 1),
(260003,500, 0, 0, 7201, 11161, 25),
(260003,525, 0, 0, 7201, 11382, 2),
(260003,550, 0, 0, 7201, 11385, 1),
(260003,575, 0, 0, 7201, 11159, 25),
(260003,600, 0, 0, 7201, 11382, 2),
(260003,625, 0, 0, 7201, 11385, 1),
(260003,650, 0, 0, 7201, 11160, 25),
(260003,675, 0, 0, 7201, 11382, 2),
(260003,700, 0, 0, 7201, 11385, 1),
(260003,725, 0, 0, 7201, 11161, 25),
(260003,750, 0, 0, 7201, 11382, 2),
(260003,775, 0, 0, 7201, 11385, 1),
(260003,800, 0, 0, 7201, 11159, 25),
(260003,825, 0, 0, 7201, 11382, 2),
(260003,850, 0, 0, 7201, 11385, 1),
(260003,875, 0, 0, 7201, 11160, 25),
(260003,900, 0, 0, 7201, 11382, 2),
(260003,925, 0, 0, 7201, 11385, 1),
(260003,950, 0, 0, 7201, 11161, 25),
(260003,975, 0, 0, 7201, 11382, 2),
(260003,1000, 0, 0, 7201, 11385, 1),
(260003,1025, 0, 0, 7201, 11159, 25),
(260003,1050, 0, 0, 7201, 11382, 2),
(260003,1075, 0, 0, 7201, 11385, 1),
(260003,1100, 0, 0, 7201, 11160, 25),
(260003,1125, 0, 0, 7201, 11382, 2),
(260003,1150, 0, 0, 7201, 11385, 1),
(260003,1200, 0, 0, 7201, 11161, 25),
(260003,1235, 0, 0, 7201, 11382, 2),
(260003,1270, 0, 0, 7201, 11385, 1),
(260003,1305, 0, 0, 7201, 11159, 25),
(260003,1340, 0, 0, 7201, 11382, 2),
(260003,1375, 0, 0, 7201, 11385, 1),
(260003,1410, 0, 0, 7201, 11160, 25),
(260003,1445, 0, 0, 7201, 11382, 2),
(260003,1480, 0, 0, 7201, 11385, 1),
(260003,1500, 0, 0, 7201, 11161, 25),
-- Advance Contribution Reward --
(260001,0, 0, 1, 7201, 11463, 1),
(260001,0, 0, 1, 7201, 11464, 1),
(260001,0, 0, 1, 7201, 11163, 1),
(260001,0, 0, 1, 7201, 11159, 5),
(260001,0, 0, 1, 7201, 11160, 5),
(260001,0, 0, 1, 7201, 11161, 5),
(260001,0, 0, 2, 7201, 12506, 1),
(260001,0, 0, 2, 7201, 10355, 1),
(260001,0, 0, 2, 7201, 11163, 1),
(260001,0, 0, 2, 7201, 11159, 5),
(260001,0, 0, 2, 7201, 11160, 5),
(260001,0, 0, 2, 7201, 11161, 5);
END;

BIN
schemas/init.sql Normal file

Binary file not shown.

View File

@@ -1,5 +1,7 @@
BEGIN;
ALTER TABLE users ADD COLUMN IF NOT EXISTS psn_id TEXT;
ALTER TABLE public.sign_sessions ADD COLUMN id SERIAL;
ALTER TABLE public.sign_sessions ADD CONSTRAINT sign_sessions_pkey PRIMARY KEY (id);

View File

@@ -7,8 +7,8 @@ CREATE TABLE IF NOT EXISTS tower (
tsp INT,
block1 INT,
block2 INT,
skills TEXT DEFAULT '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0',
gems TEXT DEFAULT '0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'
skills TEXT,
gems TEXT
);
ALTER TABLE IF EXISTS guild_characters

View File

@@ -0,0 +1,5 @@
BEGIN;
UPDATE goocoo SET goocoo0=NULL, goocoo1=NULL, goocoo2=NULL, goocoo3=NULL, goocoo4=NULL;
END;

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,6 @@
BEGIN;
UPDATE guilds SET item_box=NULL;
UPDATE users SET item_box=NULL;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE IF EXISTS public.characters RENAME hrp TO hr;
END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE guilds ADD COLUMN IF NOT EXISTS room_rp INT DEFAULT 0;
ALTER TABLE guilds ADD COLUMN IF NOT EXISTS room_expiry TIMESTAMP WITHOUT TIME ZONE;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE public.characters ADD COLUMN IF NOT EXISTS conquest_data BYTEA;
END;

View File

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

View File

@@ -0,0 +1,23 @@
BEGIN;
CREATE TABLE IF NOT EXISTS paper_data_gifts (
id serial PRIMARY KEY,
gift_id integer,
item_id integer,
unk0 integer,
unk1 integer,
chance integer
);
CREATE TABLE IF NOT EXISTS paper_data (
id serial PRIMARY KEY,
paper_type integer,
paper_id integer,
option1 integer,
option2 integer,
option3 integer,
option4 integer,
option5 integer
);
END;

View File

@@ -0,0 +1,14 @@
BEGIN;
CREATE TABLE IF NOT EXISTS weekly_seibatu_ranking_reward (
id serial PRIMARY KEY,
reward_id integer,
index0 integer,
index1 integer,
index2 integer,
distribution_type integer,
item_id integer,
amount integer
);
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;

119
server/api/api_server.go Normal file
View File

@@ -0,0 +1,119 @@
package api
import (
"context"
_config "erupe-ce/config"
"fmt"
"net/http"
"os"
"sync"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type Config struct {
Logger *zap.Logger
DB *sqlx.DB
ErupeConfig *_config.Config
}
// APIServer is Erupes Standard API interface
type APIServer struct {
sync.Mutex
logger *zap.Logger
erupeConfig *_config.Config
db *sqlx.DB
httpServer *http.Server
isShuttingDown bool
}
// NewAPIServer creates a new Server type.
func NewAPIServer(config *Config) *APIServer {
s := &APIServer{
logger: config.Logger,
erupeConfig: config.ErupeConfig,
db: config.DB,
httpServer: &http.Server{},
}
return s
}
func (s *APIServer) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.logger.Info("Received " + r.Method + " request from " + r.RemoteAddr + "path" + r.RequestURI + "\n")
next.ServeHTTP(w, r)
})
}
// Start starts the server in a new goroutine.
func (s *APIServer) Start() error {
// Set up the routes responsible for serving the launcher HTML, serverlist, unique name check, and JP auth.
r := mux.NewRouter()
r.HandleFunc("/launcher", s.Launcher)
r.HandleFunc("/login", s.Login)
r.HandleFunc("/register", s.Register)
r.HandleFunc("/character/create", s.CreateCharacter)
r.HandleFunc("/character/delete", s.DeleteCharacter)
r.HandleFunc("/character/export", s.ExportSave)
r.HandleFunc("/api/ss/bbs/upload.php", s.ScreenShot)
r.HandleFunc("/api/ss/bbs/{id}", s.ScreenShotGet)
r.HandleFunc("/v1/crypt/commonkey/rsa", s.CapLinkCrypt)
//Captured Links from messing around with caplink (wii u doesnt use crypt handle)
//Captured so the poor soul who has to reverse this has something to go off..
//CA:: is a search term for a function related...
// r.HandleFunc("/v1/profile&unique_id=", s.CapLink) //CRASH!
// r.HandleFunc("/v1/profile/content/list&unique_id=", s.CapLink) //CRASH!
// r.HandleFunc("/v1/friend/request/send/list", s.CapLink)
// r.HandleFunc("/v1/friend/request/receive/list", s.CapLink)
// r.HandleFunc("/v1/friend/list", s.CapLink)
// r.HandleFunc("/v1/friend/tag/content/list", s.CapLink)
// r.HandleFunc("/v1/chat/group/list", s.CapLink)
// r.HandleFunc("/v1/content/invite/list", s.CapLink)
// r.HandleFunc("/v1/point/get ", s.CapLink)
// r.HandleFunc("/v1/point/item/list&target=3", s.CapLink)
// r.HandleFunc("/v1/user/search/list&keyword_type=0&keyword=STRING", s.CapLink)
// r.HandleFunc("/v1/user/search/list&keyword_type=1&keyword=STRING", s.CapLink)
// r.HandleFunc("/v1/user/search/list&keyword_type=2&keyword=STRING", s.CapLink)
//Cup Icon Triggers MSG_MHF_APPLY_DIST_ITEM -> MSG_MHF_ENUMERATE_DIST_ITEM
r.Use(s.loggingMiddleware)
handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r)
s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler)
s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.API.Port)
serveError := make(chan error, 1)
go func() {
if err := s.httpServer.ListenAndServe(); err != nil {
// Send error if any.
serveError <- err
}
}()
// Get the error from calling ListenAndServe, otherwise assume it's good after 250 milliseconds.
select {
case err := <-serveError:
return err
case <-time.After(250 * time.Millisecond):
return nil
}
}
// Shutdown exits the server gracefully.
func (s *APIServer) Shutdown() {
s.logger.Debug("Shutting down")
s.Lock()
s.isShuttingDown = true
s.Unlock()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.httpServer.Shutdown(ctx); err != nil {
// Just warn because we are shutting down the server anyway.
s.logger.Warn("Got error on httpServer shutdown", zap.Error(err))
}
}

View File

@@ -1,4 +1,4 @@
package signv2server
package api
import (
"context"
@@ -10,7 +10,7 @@ import (
"golang.org/x/crypto/bcrypt"
)
func (s *Server) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) {
func (s *APIServer) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) {
// Create salted hash of user password
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
@@ -32,7 +32,7 @@ func (s *Server) createNewUser(ctx context.Context, username string, password st
return id, rights, err
}
func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) {
func (s *APIServer) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) {
loginToken := token.Generate(16)
var tid uint32
err := s.db.QueryRowContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, loginToken).Scan(&tid)
@@ -42,7 +42,7 @@ func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, stri
return tid, loginToken, nil
}
func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) {
func (s *APIServer) userIDFromToken(ctx context.Context, token string) (uint32, error) {
var userID uint32
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
if err == sql.ErrNoRows {
@@ -53,10 +53,10 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, err
return userID, nil
}
func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) {
func (s *APIServer) createCharacter(ctx context.Context, userID uint32) (Character, error) {
var character Character
err := s.db.GetContext(ctx, &character,
"SELECT id, name, is_female, weapon_type, hrp, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1",
"SELECT id, name, is_female, weapon_type, hr, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1",
userID,
)
if err == sql.ErrNoRows {
@@ -68,17 +68,17 @@ func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character,
err = s.db.GetContext(ctx, &character, `
INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login
hr, gr, weapon_type, last_login
)
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
RETURNING id, name, is_female, weapon_type, hrp, gr, last_login`,
RETURNING id, name, is_female, weapon_type, hr, gr, last_login`,
userID, uint32(time.Now().Unix()),
)
}
return character, err
}
func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error {
func (s *APIServer) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error {
var isNew bool
err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
if err != nil {
@@ -92,11 +92,11 @@ func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint
return err
}
func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) {
func (s *APIServer) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) {
var characters []Character
err := s.db.SelectContext(
ctx, &characters, `
SELECT id, name, is_female, weapon_type, hrp, gr, last_login
SELECT id, name, is_female, weapon_type, hr, gr, last_login
FROM characters
WHERE user_id = $1 AND deleted = false AND is_new_character = false ORDER BY id ASC`,
uid,
@@ -107,7 +107,7 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Charac
return characters, nil
}
func (s *Server) getReturnExpiry(uid uint32) time.Time {
func (s *APIServer) getReturnExpiry(uid uint32) time.Time {
var returnExpiry, lastLogin time.Time
s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid)
if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
@@ -124,7 +124,7 @@ func (s *Server) getReturnExpiry(uid uint32) time.Time {
return returnExpiry
}
func (s *Server) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) {
func (s *APIServer) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) {
row := s.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", cid, uid)
result := make(map[string]interface{})
err := row.MapScan(result)

View File

@@ -1,15 +1,24 @@
package signv2server
package api
import (
"database/sql"
"encoding/json"
"encoding/xml"
"errors"
_config "erupe-ce/config"
"erupe-ce/server/channelserver"
"fmt"
"image"
"image/jpeg"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/lib/pq"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
@@ -21,9 +30,9 @@ const (
)
type LauncherResponse struct {
Banners []_config.SignV2Banner `json:"banners"`
Messages []_config.SignV2Message `json:"messages"`
Links []_config.SignV2Link `json:"links"`
Banners []_config.APISignBanner `json:"banners"`
Messages []_config.APISignMessage `json:"messages"`
Links []_config.APISignLink `json:"links"`
}
type User struct {
@@ -37,7 +46,7 @@ type Character struct {
Name string `json:"name"`
IsFemale bool `json:"isFemale" db:"is_female"`
Weapon uint32 `json:"weapon" db:"weapon_type"`
HR uint32 `json:"hr" db:"hrp"`
HR uint32 `json:"hr" db:"hr"`
GR uint32 `json:"gr"`
LastLogin int32 `json:"lastLogin" db:"last_login"`
}
@@ -66,7 +75,7 @@ type ExportData struct {
Character map[string]interface{} `json:"character"`
}
func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData {
func (s *APIServer) newAuthData(userID uint32, userRights uint32, userTokenID uint32, userToken string, characters []Character) AuthData {
resp := AuthData{
CurrentTS: uint32(channelserver.TimeAdjusted().Unix()),
ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()),
@@ -77,10 +86,10 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
Token: userToken,
},
Characters: characters,
PatchServer: s.erupeConfig.SignV2.PatchServer,
PatchServer: s.erupeConfig.API.PatchServer,
Notices: []string{},
}
if s.erupeConfig.DevModeOptions.MaxLauncherHR {
if s.erupeConfig.DebugOptions.MaxLauncherHR {
for i := range resp.Characters {
resp.Characters[i].HR = 7
}
@@ -93,8 +102,8 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
ID: uint32(channelserver.TimeWeekStart().Unix()),
Start: uint32(channelserver.TimeWeekStart().Add(-time.Duration(s.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()),
End: uint32(channelserver.TimeWeekNext().Unix()),
SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets,
GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets,
SoloTickets: s.erupeConfig.GameplayOptions.MezFesSoloTickets,
GroupTickets: s.erupeConfig.GameplayOptions.MezFesGroupTickets,
Stalls: stalls,
}
if !s.erupeConfig.HideLoginNotice {
@@ -103,16 +112,16 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID uint3
return resp
}
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
func (s *APIServer) Launcher(w http.ResponseWriter, r *http.Request) {
var respData LauncherResponse
respData.Banners = s.erupeConfig.SignV2.Banners
respData.Messages = s.erupeConfig.SignV2.Messages
respData.Links = s.erupeConfig.SignV2.Links
respData.Banners = s.erupeConfig.API.Banners
respData.Messages = s.erupeConfig.API.Messages
respData.Links = s.erupeConfig.API.Links
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(respData)
}
func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Username string `json:"username"`
@@ -164,7 +173,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(respData)
}
func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Username string `json:"username"`
@@ -204,7 +213,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(respData)
}
func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
func (s *APIServer) CreateCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Token string `json:"token"`
@@ -226,14 +235,14 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
return
}
if s.erupeConfig.DevModeOptions.MaxLauncherHR {
if s.erupeConfig.DebugOptions.MaxLauncherHR {
character.HR = 7
}
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(character)
}
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
func (s *APIServer) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Token string `json:"token"`
@@ -258,7 +267,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(struct{}{})
}
func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) {
func (s *APIServer) ExportSave(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Token string `json:"token"`
@@ -286,3 +295,138 @@ func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(save)
}
func (s *APIServer) ScreenShotGet(w http.ResponseWriter, r *http.Request) {
// Get the 'id' parameter from the URL
token := mux.Vars(r)["id"]
var tokenPattern = regexp.MustCompile(`[A-Za-z0-9]+`)
if !tokenPattern.MatchString(token) || token == "" {
http.Error(w, "Not Valid Token", http.StatusBadRequest)
}
// Open the image file
safePath := s.erupeConfig.Screenshots.OutputDir
path := filepath.Join(safePath, fmt.Sprintf("%s.jpg", token))
result, err := verifyPath(path, safePath)
if err != nil {
fmt.Println("Error " + err.Error())
} else {
fmt.Println("Canonical: " + result)
file, err := os.Open(result)
if err != nil {
http.Error(w, "Image not found", http.StatusNotFound)
return
}
defer file.Close()
// Set content type header to image/jpeg
w.Header().Set("Content-Type", "image/jpeg")
// Copy the image content to the response writer
if _, err := io.Copy(w, file); err != nil {
http.Error(w, "Unable to send image", http.StatusInternalServerError)
return
}
}
}
func (s *APIServer) ScreenShot(w http.ResponseWriter, r *http.Request) {
// Create a struct representing the XML result
type Result struct {
XMLName xml.Name `xml:"result"`
Code string `xml:"code"`
}
// Set the Content-Type header to specify that the response is in XML format
w.Header().Set("Content-Type", "text/xml")
result := Result{Code: "200"}
if !s.erupeConfig.Screenshots.Enabled {
result = Result{Code: "400"}
} else {
if r.Method != http.MethodPost {
result = Result{Code: "405"}
}
// Get File from Request
file, _, err := r.FormFile("img")
if err != nil {
result = Result{Code: "400"}
}
var tokenPattern = regexp.MustCompile(`[A-Za-z0-9]+`)
token := r.FormValue("token")
if !tokenPattern.MatchString(token) || token == "" {
result = Result{Code: "401"}
}
// Validate file
img, _, err := image.Decode(file)
if err != nil {
result = Result{Code: "400"}
}
safePath := s.erupeConfig.Screenshots.OutputDir
path := filepath.Join(safePath, fmt.Sprintf("%s.jpg", token))
verified, err := verifyPath(path, safePath)
if err != nil {
result = Result{Code: "500"}
} else {
_, err = os.Stat(safePath)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(safePath, os.ModePerm)
if err != nil {
s.logger.Error("Error writing screenshot, could not create folder")
result = Result{Code: "500"}
}
} else {
s.logger.Error("Error writing screenshot")
result = Result{Code: "500"}
}
}
// Create or open the output file
outputFile, err := os.Create(verified)
if err != nil {
result = Result{Code: "500"}
}
defer outputFile.Close()
// Encode the image and write it to the file
err = jpeg.Encode(outputFile, img, &jpeg.Options{Quality: s.erupeConfig.Screenshots.UploadQuality})
if err != nil {
s.logger.Error("Error writing screenshot, could not write file", zap.Error(err))
result = Result{Code: "500"}
}
}
}
// Marshal the struct into XML
xmlData, err := xml.Marshal(result)
if err != nil {
http.Error(w, "Unable to marshal XML", http.StatusInternalServerError)
return
}
// Write the XML response with a 200 status code
w.WriteHeader(http.StatusOK)
w.Write(xmlData)
}
func (s *APIServer) CapLinkCrypt(w http.ResponseWriter, r *http.Request) {
// Extract query parameters
queryParams := r.URL.Query()
// Extracting specific query parameters
clientPubKey := queryParams.Get("client_pub_key")
deviceID := queryParams.Get("device_id")
platformID := queryParams.Get("platform_id")
contentID := queryParams.Get("content_id")
serverVersion := queryParams.Get("server_version")
clientVersion := queryParams.Get("client_version")
// Your business logic to generate response based on query parameters
response := fmt.Sprintf("Received request with client_pub_key: %s, device_id: %s, platform_id: %s, content_id: %s, server_version: %s, client_version: %s", clientPubKey, deviceID, platformID, contentID, serverVersion, clientVersion)
// Write response headers
w.WriteHeader(http.StatusOK)
// Write response body
w.Write([]byte(response))
}

37
server/api/utils.go Normal file
View File

@@ -0,0 +1,37 @@
package api
import (
"errors"
"fmt"
"path/filepath"
)
func inTrustedRoot(path string, trustedRoot string) error {
for path != "/" {
path = filepath.Dir(path)
if path == trustedRoot {
return nil
}
}
return errors.New("path is outside of trusted root")
}
func verifyPath(path string, trustedRoot string) (string, error) {
c := filepath.Clean(path)
fmt.Println("Cleaned path: " + c)
r, err := filepath.EvalSymlinks(c)
if err != nil {
fmt.Println("Error " + err.Error())
return c, errors.New("Unsafe or invalid path specified")
}
err = inTrustedRoot(r, trustedRoot)
if err != nil {
fmt.Println("Error " + err.Error())
return r, errors.New("Unsafe or invalid path specified")
} else {
return r, nil
}
}

View File

@@ -3,12 +3,14 @@ package channelserver
import (
"encoding/binary"
"erupe-ce/common/mhfcourse"
"erupe-ce/common/mhfitem"
"erupe-ce/common/mhfmon"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"fmt"
"io"
"log"
"net"
"strings"
"time"
@@ -31,7 +33,7 @@ func stubEnumerateNoResults(s *Session, ackHandle uint32) {
func doAckEarthSucceed(s *Session, ackHandle uint32, data []*byteframe.ByteFrame) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(s.server.erupeConfig.DevModeOptions.EarthIDOverride))
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(uint32(len(data)))
@@ -108,14 +110,20 @@ func handleMsgSysAck(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysTerminalLog)
//Type 1: 1 Type 2: 8 Quest? Unk2 Quest ID
//Type 1: 71 Type 2: 8 Quest Tower? Unk2 Quest ID
//Type 1: 79 Type 2: 99 Caravan Points (Pallone?)
//Type 1: 10 Type 2: 5 Zenny?? (Gained via Selling Items at Quest End and Quest End Reward)
//Type 1: 91 Type 2: 100 Unk0 Amount Unk2 // Tower Reward at Gal?
for i := range pkt.Entries {
s.server.logger.Info("SysTerminalLog",
zap.Uint8("Type1", pkt.Entries[i].Type1),
zap.Uint8("Type2", pkt.Entries[i].Type2),
zap.Int16("Unk0", pkt.Entries[i].Unk0),
zap.Int32("Unk1", pkt.Entries[i].Unk1),
zap.Int32("Unk2", pkt.Entries[i].Unk2),
zap.Int32("Unk3", pkt.Entries[i].Unk3),
zap.Int32("Unk 2 Sometimes Quest QuestID", pkt.Entries[i].Unk2),
zap.Int32("CID", pkt.Entries[i].CID),
zap.Int32s("Unk4", pkt.Entries[i].Unk4),
)
}
@@ -127,7 +135,7 @@ func handleMsgSysTerminalLog(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLogin)
if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck {
if !s.server.erupeConfig.DebugOptions.DisableTokenCheck {
var token string
err := s.server.db.QueryRow("SELECT token FROM sign_sessions ss INNER JOIN public.users u on ss.user_id = u.id WHERE token=$1 AND ss.id=$2 AND u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.LoginTokenString, pkt.LoginTokenNumber, pkt.CharID0).Scan(&token)
if err != nil {
@@ -817,93 +825,33 @@ func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
var boxContents []byte
bf := byteframe.NewByteFrame()
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
if err != nil {
s.logger.Error("Failed to get shared item box contents from db", zap.Error(err))
bf.WriteBytes(make([]byte, 4))
} else {
if len(boxContents) == 0 {
bf.WriteBytes(make([]byte, 4))
} else {
amount := len(boxContents) / 4
bf.WriteUint16(uint16(amount))
bf.WriteUint32(0x00)
bf.WriteUint16(0x00)
for i := 0; i < amount; i++ {
bf.WriteUint32(binary.BigEndian.Uint32(boxContents[i*4 : i*4+4]))
if i+1 != amount {
bf.WriteUint64(0x00)
}
}
func userGetItems(s *Session) []mhfitem.MHFItemStack {
var data []byte
var items []mhfitem.MHFItemStack
s.server.db.QueryRow(`SELECT item_box FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&data)
if len(data) > 0 {
box := byteframe.NewByteFrameFromBytes(data)
numStacks := box.ReadUint16()
box.ReadUint16() // Unused
for i := 0; i < int(numStacks); i++ {
items = append(items, mhfitem.ReadWarehouseItem(box))
}
}
return items
}
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
items := userGetItems(s)
bf := byteframe.NewByteFrame()
bf.WriteBytes(mhfitem.SerializeWarehouseItems(items))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem)
// Get item cache from DB
var boxContents []byte
var oldItems []Item
err := s.server.db.QueryRow("SELECT item_box FROM users, characters WHERE characters.id = $1 AND users.id = characters.user_id", int(s.charID)).Scan(&boxContents)
if err != nil {
s.logger.Error("Failed to get shared item box contents from db", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
amount := len(boxContents) / 4
oldItems = make([]Item, amount)
for i := 0; i < amount; i++ {
oldItems[i].ItemId = binary.BigEndian.Uint16(boxContents[i*4 : i*4+2])
oldItems[i].Amount = binary.BigEndian.Uint16(boxContents[i*4+2 : i*4+4])
}
}
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < len(pkt.Items); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemID
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemID == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
}
}
// Delete empty item stacks
for i := len(newItems) - 1; i >= 0; i-- {
if int(newItems[i].Amount) == 0 {
copy(newItems[i:], newItems[i+1:])
newItems[len(newItems)-1] = make([]Item, 1)[0]
newItems = newItems[:len(newItems)-1]
}
}
// Create new item cache
bf := byteframe.NewByteFrame()
for i := 0; i < len(newItems); i++ {
bf.WriteUint16(newItems[i].ItemId)
bf.WriteUint16(newItems[i].Amount)
}
// Upload new item cache
_, err = s.server.db.Exec("UPDATE users SET item_box = $1 FROM characters WHERE users.id = characters.user_id AND characters.id = $2", bf.Data(), int(s.charID))
if err != nil {
s.logger.Error("Failed to update shared item box contents in db", zap.Error(err))
}
newStacks := mhfitem.DiffItemStacks(userGetItems(s), pkt.UpdatedItems)
s.server.db.Exec(`UPDATE users u SET item_box=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, mhfitem.SerializeWarehouseItems(newStacks), s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
@@ -911,87 +859,83 @@ func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp)
weekCurrentStart := TimeWeekStart()
weekNextStart := TimeWeekNext()
var total, redeemed, updated uint16
var nextClaim time.Time
err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim)
var lastCheck time.Time
err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_checked FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&lastCheck)
if err != nil {
s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart)
nextClaim = weekNextStart
lastCheck = TimeAdjusted()
s.server.db.Exec("INSERT INTO stamps (character_id, hl_checked, ex_checked) VALUES ($1, $2, $2)", s.charID, TimeAdjusted())
} else {
s.server.db.Exec(fmt.Sprintf(`UPDATE stamps SET %s_checked=$1 WHERE character_id=$2`, pkt.StampType), TimeAdjusted(), s.charID)
}
if nextClaim.Before(weekCurrentStart) {
s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1, %s_next=$1 WHERE character_id=$2", pkt.StampType, pkt.StampType, pkt.StampType), weekNextStart, s.charID)
if lastCheck.Before(TimeWeekStart()) {
s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1 WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID)
updated = 1
}
s.server.db.QueryRow(fmt.Sprintf("SELECT %s_total, %s_redeemed FROM stamps WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
bf := byteframe.NewByteFrame()
bf.WriteUint16(total)
bf.WriteUint16(redeemed)
bf.WriteUint16(updated)
bf.WriteUint32(0) // Unk
bf.WriteUint32(uint32(weekCurrentStart.Unix()))
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint32(uint32(TimeWeekStart().Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp)
var total, redeemed uint16
var tktStack mhfpacket.WarehouseStack
if pkt.Unk1 == 0xA { // Yearly Sub Ex
var tktStack mhfitem.MHFItemStack
if pkt.Unk1 == 10 { // Yearly Sub Ex
s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed)
tktStack = mhfpacket.WarehouseStack{ItemID: 0x08A2, Quantity: 1}
tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 2210}, Quantity: 1}
} else {
s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
if pkt.StampType == "hl" {
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065E, Quantity: 5}
tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1630}, Quantity: 5}
} else {
tktStack = mhfpacket.WarehouseStack{ItemID: 0x065F, Quantity: 5}
tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1631}, Quantity: 5}
}
}
addWarehouseGift(s, "item", tktStack)
addWarehouseItem(s, tktStack)
bf := byteframe.NewByteFrame()
bf.WriteUint16(total)
bf.WriteUint16(redeemed)
bf.WriteUint16(0)
bf.WriteUint32(0) // Unk, but has possible values
bf.WriteUint16(tktStack.Item.ItemID)
bf.WriteUint16(tktStack.Quantity)
bf.WriteUint32(uint32(TimeWeekStart().Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func getGookData(s *Session, cid uint32) (uint16, []byte) {
var data []byte
var count uint16
bf := byteframe.NewByteFrame()
func getGoocooData(s *Session, cid uint32) [][]byte {
var goocoo []byte
var goocoos [][]byte
for i := 0; i < 5; i++ {
err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&data)
err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&goocoo)
if err != nil {
s.server.db.Exec("INSERT INTO goocoo (id) VALUES ($1)", s.charID)
return 0, bf.Data()
return goocoos
}
if err == nil && data != nil {
count++
if s.charID == cid && count == 1 {
goocoo := byteframe.NewByteFrameFromBytes(data)
bf.WriteBytes(goocoo.ReadBytes(4))
d := goocoo.ReadBytes(2)
bf.WriteBytes(d)
bf.WriteBytes(d)
bf.WriteBytes(goocoo.DataFromCurrent())
} else {
bf.WriteBytes(data)
}
if err == nil && goocoo != nil {
goocoos = append(goocoos, goocoo)
}
}
return count, bf.Data()
return goocoos
}
func handleMsgMhfEnumerateGuacot(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuacot)
bf := byteframe.NewByteFrame()
count, data := getGookData(s, s.charID)
bf.WriteUint16(count)
bf.WriteBytes(data)
goocoos := getGoocooData(s, s.charID)
bf.WriteUint16(uint16(len(goocoos)))
bf.WriteUint16(0)
for _, goocoo := range goocoos {
bf.WriteBytes(goocoo)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -1004,7 +948,7 @@ func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(goocoo.Index)
for i := range goocoo.Data1 {
bf.WriteUint16(goocoo.Data1[i])
bf.WriteInt16(goocoo.Data1[i])
}
for i := range goocoo.Data2 {
bf.WriteUint32(goocoo.Data2[i])
@@ -1100,10 +1044,10 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) {
column = "promo_points"
}
var value int
var value int16
err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value)
if err == nil {
if value-int(pkt.Delta) < 0 {
if value+pkt.Delta < 0 {
s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID)
} else {
s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID)
@@ -1116,9 +1060,11 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStampcardStamp)
bf := byteframe.NewByteFrame()
bf.WriteUint16(pkt.HR)
bf.WriteUint16(pkt.GR)
var stamps uint16
_ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps)
if _config.ErupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint16(pkt.GR)
}
bf.WriteUint16(stamps)
stamps += pkt.Stamps
bf.WriteUint16(stamps)
@@ -1128,13 +1074,13 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(pkt.Reward2)
bf.WriteUint16(pkt.Item2)
bf.WriteUint16(pkt.Quantity2)
addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item2, Quantity: pkt.Quantity2})
addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item2}, Quantity: pkt.Quantity2})
} else if stamps%15 == 0 {
bf.WriteUint16(1)
bf.WriteUint16(pkt.Reward1)
bf.WriteUint16(pkt.Item1)
bf.WriteUint16(pkt.Quantity1)
addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item1, Quantity: pkt.Quantity1})
addWarehouseItem(s, mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: pkt.Item1}, Quantity: pkt.Quantity1})
} else {
bf.WriteBytes(make([]byte, 8))
}
@@ -1150,15 +1096,65 @@ func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfKickExportForce(s *Session, p mhfpacket.MHFPacket) {}
func cleanupEarthStatus(s *Session) {
s.server.db.Exec(`DELETE FROM events WHERE event_type='earth'`)
s.server.db.Exec(`UPDATE characters SET conquest_data=NULL`)
}
func generateEarthStatusTimestamps(s *Session, start uint32, debug bool) []uint32 {
timestamps := make([]uint32, 4)
midnight := TimeMidnight()
if start == 0 || TimeAdjusted().Unix() > int64(start)+1814400 {
cleanupEarthStatus(s)
start = uint32(midnight.Add(24 * time.Hour).Unix())
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('earth', to_timestamp($1)::timestamp without time zone)", start)
}
if debug {
timestamps[0] = uint32(TimeWeekStart().Unix())
timestamps[1] = uint32(TimeWeekNext().Unix())
timestamps[2] = uint32(TimeWeekNext().Add(time.Duration(7) * time.Hour * 24).Unix())
timestamps[3] = uint32(TimeWeekNext().Add(time.Duration(14) * time.Hour * 24).Unix())
} else {
timestamps[0] = start
timestamps[1] = timestamps[0] + 604800
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 604800
}
return timestamps
}
func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride)
for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride {
if _config.ErupeConfig.RealClientMode <= _config.G9 {
var earthTimestamps []uint32
var debug = s.server.erupeConfig.EarthDebug
earthId, earthStart := int32(0x01BEEFEE), uint32(0)
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='earth'")
if rows == nil {
log.Println("No rows found")
} else {
for rows.Next() {
rows.Scan(&earthId, &earthStart)
}
}
earthTimestamps = generateEarthStatusTimestamps(s, earthStart, debug)
// Conquest
if uint32(TimeAdjusted().Unix()) > earthTimestamps[0] {
bf.WriteUint32(earthTimestamps[0]) // Start
bf.WriteUint32(earthTimestamps[1]) // End
bf.WriteInt32(1) //Conquest Earth Status ID //1 and 2 UNK the difference
bf.WriteInt32(earthId) //ID
} else {
bf.WriteUint32(earthTimestamps[1]) // Start
bf.WriteUint32(earthTimestamps[2]) // End
bf.WriteInt32(2) //Conquest Earth Status ID //1 and 2 UNK the difference
bf.WriteInt32(earthId) //ID
}
for i, m := range s.server.erupeConfig.EarthMonsters {
//Changed from G9 to G8 to get conquest working in g9.1
if _config.ErupeConfig.RealClientMode <= _config.G8 {
if i == 3 {
break
}
@@ -1168,6 +1164,50 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
}
bf.WriteInt32(m)
}
// Pallone
if uint32(TimeAdjusted().Unix()) > earthTimestamps[1] {
bf.WriteUint32(earthTimestamps[1]) // Start
bf.WriteUint32(earthTimestamps[2]) // End
bf.WriteInt32(11) //Pallone Earth Status ID //11 is Fest //12 is Reward
bf.WriteInt32(earthId + 1) //ID
} else {
bf.WriteUint32(earthTimestamps[2]) // Start
bf.WriteUint32(earthTimestamps[3]) // End
bf.WriteInt32(12) //Pallone Earth Status ID //11 is Fest //12 is Reward
bf.WriteInt32(earthId + 1) //ID
}
for i, m := range s.server.erupeConfig.EarthMonsters {
//Changed from G9 to G8 to get conquest working in g9.1
if _config.ErupeConfig.RealClientMode <= _config.G8 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
// Tower
if uint32(TimeAdjusted().Unix()) > earthTimestamps[2] {
bf.WriteUint32(earthTimestamps[2]) // Start
bf.WriteUint32(earthTimestamps[3]) // End
bf.WriteInt32(21) //Tower Earth Status ID
bf.WriteInt32(earthId + 2) //ID
for i, m := range s.server.erupeConfig.EarthMonsters {
if _config.ErupeConfig.RealClientMode <= _config.G8 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -1183,19 +1223,21 @@ func handleMsgMhfGetEarthValue(s *Session, p mhfpacket.MHFPacket) {
switch pkt.ReqType {
case 1:
earthValues = []EarthValues{
{[]uint32{1, 312, 0, 0, 0, 0}},
{[]uint32{2, 99, 0, 0, 0, 0}},
// {Block, DureSlays, Unk, Unk, Unk, Unk}
{[]uint32{1, 100, 0, 0, 0, 0}},
{[]uint32{2, 100, 0, 0, 0, 0}},
}
case 2:
earthValues = []EarthValues{
// {Block, Floors?, Unk, Unk, Unk, Unk}
{[]uint32{1, 5771, 0, 0, 0, 0}},
{[]uint32{2, 1847, 0, 0, 0, 0}},
}
case 3:
earthValues = []EarthValues{
{[]uint32{1001, 36, 0, 0, 0, 0}},
{[]uint32{9001, 3, 0, 0, 0, 0}},
{[]uint32{9002, 10, 300, 0, 0, 0}},
{[]uint32{1001, 36, 0, 0, 0, 0}}, //getTouhaHistory
{[]uint32{9001, 3, 0, 0, 0, 0}}, //getKohouhinDropStopFlag // something to do with ttcSetDisableFlag?
{[]uint32{9002, 10, 300, 0, 0, 0}}, //getKohouhinForceValue
}
}

View File

@@ -7,35 +7,47 @@ import (
"erupe-ce/network/mhfpacket"
)
// Handler BBS handles all the interactions with the for the screenshot sending to bulitin board functionality. For it to work it requires the API to be hosted somehwere. This implementation supports discord.
// Checks the status of the user to see if they can use Bulitin Board yet
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
//Post Screenshot pauses till this succeedes
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(200)
bf.WriteUint32(200) //HTTP Status Codes //200 Success //404 You wont be able to post for a certain amount of time after creating your character //401/500 A error occured server side
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// Checks the status of Bultin Board Server to see if authenticated
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(200)
bf.WriteUint32(401)
bf.WriteUint32(401)
bf.WriteUint32(200) //200 Success //4XX Authentication has expired Please re-authenticate //5XX
bf.WriteUint32(401) //unk http status?
bf.WriteUint32(401) //unk http status?
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// Tells the game client what host port and gives the bultin board article a token
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
bf := byteframe.NewByteFrame()
articleToken := token.Generate(40)
bf.WriteUint32(200)
bf.WriteUint32(80)
bf.WriteUint32(200) //http status //200 success //4XX An error occured server side
bf.WriteUint32(s.server.erupeConfig.Screenshots.Port)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false))
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.Screenshots.Host, 64, false))
//pkt.unk1[3] == Changes sometimes?
if s.server.erupeConfig.Screenshots.Enabled && s.server.erupeConfig.Discord.Enabled {
s.server.DiscordScreenShotSend(pkt.Name, pkt.Title, pkt.Description, articleToken)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse"
ps "erupe-ce/common/pascalstring"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap"
@@ -92,10 +93,11 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
if mhfcourse.CourseExists(30, s.courses) {
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
}
bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
bf.WriteUint32(cafeTime)
if _config.ErupeConfig.RealClientMode >= _config.ZZ {
bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -40,6 +40,29 @@ type Ryoudama struct {
Score []int32
}
type CaravanMyScore struct {
Unk0 int32
MyScore int32
Unk2 int32
Unk3 int32
}
type CaravanMyRank struct {
Unk0 int32
Unk1 int32
Unk2 int32
}
type CaravanRyoudanRanking struct {
Score int32
HuntingGroupId uint32
Name string
}
type CaravanPersonalRanking struct {
Score int32
Name string
}
func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRyoudama)
var data []*byteframe.ByteFrame
@@ -73,9 +96,25 @@ func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) {
//Works for Tower but not Conquest
//Conquest: Unk0 0 Unk1 2 Unk2 1
type TinyBinItem struct {
ItemID uint16
Quantity uint16
}
tinyBinItems := []TinyBinItem{{7, 2}, {8, 1}, {9, 1}, {300, 4}, {10, 1}}
pkt := p.(*mhfpacket.MsgMhfGetTinyBin)
// requested after conquest quests
doAckBufSucceed(s, pkt.AckHandle, []byte{})
bf := byteframe.NewByteFrame()
bf.SetLE()
for _, items := range tinyBinItems {
bf.WriteUint16(items.ItemID)
bf.WriteUint16(items.Quantity)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {
@@ -85,39 +124,93 @@ func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCaravanMyScore)
ryoudama := []CaravanMyScore{{6, 60900, 6, 6}}
var data []*byteframe.ByteFrame
/*
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
*/
for _, score := range ryoudama {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score.Unk0)
bf.WriteInt32(score.MyScore)
bf.WriteInt32(score.Unk2)
bf.WriteInt32(score.Unk3)
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCaravanRanking)
var data []*byteframe.ByteFrame
/* RYOUDAN
bf.WriteInt32(1)
bf.WriteUint32(2)
bf.WriteBytes(stringsupport.PaddedString("Test", 26, true))
*/
/* PERSONAL
bf.WriteInt32(1)
bf.WriteBytes(stringsupport.PaddedString("Test", 14, true))
*/
// 1 = Top 100 when this Unk2 is the
// 4 = Guild Score
// 5 = Guild Team Individual Score
// 2 = Personal Score
switch pkt.Operation {
case 1:
personalRanking := []CaravanPersonalRanking{{60900, "Hunter 0"}, {20, "Hunter a"}, {4, "Hunter b"}, {4, "Hunter c"}, {2, "Hunter d"}, {1, "Hunter e"}}
for _, score := range personalRanking {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score.Score)
bf.WriteBytes(stringsupport.PaddedString(score.Name, 14, true))
data = append(data, bf)
}
case 2:
personalRanking := []CaravanPersonalRanking{{60900, "Hunter 0"}, {20, "Hunter a"}, {4, "Hunter b"}, {4, "Hunter c"}, {2, "Hunter d"}, {1, "Hunter e"}}
for _, score := range personalRanking {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score.Score)
bf.WriteBytes(stringsupport.PaddedString(score.Name, 14, true))
data = append(data, bf)
}
case 3:
ryoudama := []CaravanRyoudanRanking{{5, 1, "Clan a"}, {4, 2, "Clan b"}, {3, 3, "Clan c"}, {2, 4, "Clan d"}, {1, 5, "Clan e"}, {0, 6, "Clan f"}}
for _, score := range ryoudama {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score.Score)
bf.WriteUint32(score.HuntingGroupId)
bf.WriteBytes(stringsupport.PaddedString(score.Name, 26, true))
data = append(data, bf)
}
case 4:
ryoudama := []CaravanRyoudanRanking{{5, 1, "Clan a"}, {4, 2, "Clan b"}, {3, 3, "Clan c"}, {2, 4, "Clan d"}, {1, 5, "Clan e"}, {0, 6, "Clan f"}}
for _, score := range ryoudama {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score.Score)
bf.WriteUint32(score.HuntingGroupId)
bf.WriteBytes(stringsupport.PaddedString(score.Name, 26, true))
data = append(data, bf)
}
case 5:
//Unk2 is Hunting Team ID
//Having more than 5 in array stops loading
// Do Select ... Where HunterTeamID = pkt.unk2
personalRanking := []CaravanPersonalRanking{{10, "Clan Hunter 1"}, {9, "Clan Hunter 2"}, {8, "Clan Hunter 3"}, {7, "Clan Hunter 4"}, {6, "Clan Hunter 5"}}
for _, score := range personalRanking {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score.Score)
bf.WriteBytes(stringsupport.PaddedString(score.Name, 14, true))
data = append(data, bf)
}
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCaravanMyRank)
// if value 1 is 0 ! on the route
unk := []CaravanMyRank{{6, 6, 6}}
//Personal - General Unk 1 : 1
var data []*byteframe.ByteFrame
/*
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
*/
for _, unkData := range unk {
bf := byteframe.NewByteFrame()
bf.WriteInt32(unkData.Unk0)
bf.WriteInt32(unkData.Unk1)
bf.WriteInt32(unkData.Unk2)
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}

View File

@@ -1,8 +1,10 @@
package channelserver
import (
"crypto/rand"
"encoding/hex"
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcid"
"erupe-ce/common/mhfcourse"
"erupe-ce/common/token"
"erupe-ce/config"
@@ -58,7 +60,7 @@ func init() {
}
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
}
func sendServerChatMessage(s *Session, message string) {
@@ -86,28 +88,97 @@ func sendServerChatMessage(s *Session, message string) {
func parseChatCommand(s *Session, command string) {
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
switch args[0] {
case commands["Ban"].Prefix:
if s.isOp() {
if len(args) > 1 {
var expiry time.Time
if len(args) > 2 {
var length int
var unit string
n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit)
if err == nil && n == 2 {
switch unit {
case "s", "second", "seconds":
expiry = time.Now().Add(time.Duration(length) * time.Second)
case "m", "mi", "minute", "minutes":
expiry = time.Now().Add(time.Duration(length) * time.Minute)
case "h", "hour", "hours":
expiry = time.Now().Add(time.Duration(length) * time.Hour)
case "d", "day", "days":
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24)
case "mo", "month", "months":
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30)
case "y", "year", "years":
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
return
}
}
cid := mhfcid.ConvertCID(args[1])
if cid > 0 {
var uid uint32
var uname string
err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname)
if err == nil {
if expiry.IsZero() {
s.server.db.Exec(`INSERT INTO bans VALUES ($1)
ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid)
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname))
} else {
s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2)
ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry)
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime)))
}
s.server.DisconnectUser(uid)
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.noUser)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.invalid)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
}
} else {
sendServerChatMessage(s, s.server.i18n.commands.noOp)
}
case commands["Timer"].Prefix:
if commands["Timer"].Enabled || s.isOp() {
var state bool
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state)
s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID)
if state {
sendServerChatMessage(s, s.server.i18n.commands.timer.disabled)
} else {
sendServerChatMessage(s, s.server.i18n.commands.timer.enabled)
}
} else {
sendDisabledCommandMessage(s, commands["Timer"])
}
case commands["PSN"].Prefix:
if commands["PSN"].Enabled {
if commands["PSN"].Enabled || s.isOp() {
if len(args) > 1 {
var exists int
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
if exists == 0 {
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1]))
}
} else {
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["PSN"])
}
case commands["Reload"].Prefix:
if commands["Reload"].Enabled {
sendServerChatMessage(s, s.server.dict["commandReload"])
if commands["Reload"].Enabled || s.isOp() {
sendServerChatMessage(s, s.server.i18n.commands.reload)
var temp mhfpacket.MHFPacket
deleteNotif := byteframe.NewByteFrame()
for _, object := range s.stage.objects {
@@ -167,21 +238,21 @@ func parseChatCommand(s *Session, command string) {
sendDisabledCommandMessage(s, commands["Reload"])
}
case commands["KeyQuest"].Prefix:
if commands["KeyQuest"].Enabled {
if commands["KeyQuest"].Enabled || s.isOp() {
if s.server.erupeConfig.RealClientMode < _config.G10 {
sendServerChatMessage(s, s.server.dict["commandKqfVersion"])
sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
} else {
if len(args) > 1 {
if args[1] == "get" {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, s.kqf))
} else if args[1] == "set" {
if len(args) > 2 && len(args[2]) == 16 {
hexd, _ := hex.DecodeString(args[2])
s.kqf = hexd
s.kqfOverride = true
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success)
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix))
}
}
}
@@ -190,23 +261,23 @@ func parseChatCommand(s *Session, command string) {
sendDisabledCommandMessage(s, commands["KeyQuest"])
}
case commands["Rights"].Prefix:
if commands["Rights"].Enabled {
if commands["Rights"].Enabled || s.isOp() {
if len(args) > 1 {
v, _ := strconv.Atoi(args[1])
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Rights"])
}
case commands["Course"].Prefix:
if commands["Course"].Enabled {
if commands["Course"].Enabled || s.isOp() {
if len(args) > 1 {
for _, course := range mhfcourse.Courses() {
for _, alias := range course.Aliases() {
@@ -224,11 +295,11 @@ func parseChatCommand(s *Session, command string) {
})
if ei != -1 {
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0]))
}
} else {
delta = uint32(math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0]))
}
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
if err == nil {
@@ -236,71 +307,71 @@ func parseChatCommand(s *Session, command string) {
}
updateRights(s)
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0]))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0]))
}
return
}
}
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Course"])
}
case commands["Raviente"].Prefix:
if commands["Raviente"].Enabled {
if commands["Raviente"].Enabled || s.isOp() {
if len(args) > 1 {
if s.server.getRaviSemaphore() != nil {
switch args[1] {
case "start":
if s.server.raviente.register[1] == 0 {
s.server.raviente.register[1] = s.server.raviente.register[3]
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success)
s.notifyRavi()
} else {
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
}
case "cm", "check", "checkmultiplier", "multiplier":
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier()))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, s.server.GetRaviMultiplier()))
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
if s.server.erupeConfig.RealClientMode == _config.ZZ {
switch args[1] {
case "sr", "sendres", "resurrection":
if s.server.raviente.state[28] > 0 {
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.success)
s.server.raviente.state[28] = 0
} else {
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
}
case "ss", "sendsed":
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
// Total BerRavi HP
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
s.server.raviente.support[1] = HP
case "rs", "reqsed":
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.request)
// Total BerRavi HP
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
s.server.raviente.support[1] = HP + 1
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
}
default:
sendServerChatMessage(s, s.server.dict["commandRaviError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviError"])
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
}
} else {
sendDisabledCommandMessage(s, commands["Raviente"])
}
case commands["Teleport"].Prefix:
if commands["Teleport"].Enabled {
if commands["Teleport"].Enabled || s.isOp() {
if len(args) > 2 {
x, _ := strconv.ParseInt(args[1], 10, 16)
y, _ := strconv.ParseInt(args[2], 10, 16)
@@ -315,17 +386,31 @@ func parseChatCommand(s *Session, command string) {
MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes,
})
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Teleport"])
}
case commands["Discord"].Prefix:
if commands["Discord"].Enabled || s.isOp() {
var _token string
err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token)
if err != nil {
randToken := make([]byte, 4)
rand.Read(randToken)
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:])
s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID)
}
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token))
} else {
sendDisabledCommandMessage(s, commands["Discord"])
}
case commands["Help"].Prefix:
if commands["Help"].Enabled {
if commands["Help"].Enabled || s.isOp() {
for _, command := range commands {
if command.Enabled {
if command.Enabled || s.isOp() {
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
}
}
@@ -341,14 +426,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
_ = tmp.ReadBytes(9)
tmp.SetLE()
frame := tmp.ReadUint32()
sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
var timer bool
s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer)
if timer {
_ = tmp.ReadBytes(9)
tmp.SetLE()
frame := tmp.ReadUint32()
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.timer, frame/30/60/60, frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
}
}
}
if s.server.erupeConfig.DevModeOptions.QuestDebugTools == true && s.server.erupeConfig.DevMode {
if s.server.erupeConfig.DebugOptions.QuestTools {
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
// This is only correct most of the time
tmp.ReadBytes(20)
@@ -362,24 +451,20 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
// Parse out the real casted binary payload
var msgBinTargeted *binpacket.MsgBinTargeted
var authorLen, msgLen uint16
var msg []byte
isDiceCommand := false
var message, author string
var returnToSender bool
if pkt.MessageType == BinaryMessageTypeChat {
tmp.SetLE()
tmp.Seek(int64(0), 0)
_ = tmp.ReadUint32()
authorLen = tmp.ReadUint16()
msgLen = tmp.ReadUint16()
msg = tmp.ReadNullTerminatedBytes()
tmp.Seek(8, 0)
message = string(tmp.ReadNullTerminatedBytes())
author = string(tmp.ReadNullTerminatedBytes())
}
// Customise payload
realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted {
tmp.SetBE()
tmp.Seek(int64(0), 0)
tmp.Seek(0, 0)
msgBinTargeted = &binpacket.MsgBinTargeted{}
err := msgBinTargeted.Parse(tmp)
if err != nil {
@@ -388,18 +473,18 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
}
realPayload = msgBinTargeted.RawDataPayload
} else if pkt.MessageType == BinaryMessageTypeChat {
if msgLen == 6 && string(msg) == "@dice" {
isDiceCommand = true
roll := byteframe.NewByteFrame()
roll.WriteInt16(1) // Unk
roll.SetLE()
roll.WriteUint16(4) // Unk
roll.WriteUint16(authorLen)
dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1)
roll.WriteUint16(uint16(len(dice) + 1))
roll.WriteNullTerminatedBytes([]byte(dice))
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
realPayload = roll.Data()
if message == "@dice" {
returnToSender = true
m := binpacket.MsgBinChat{
Type: BinaryMessageTypeChat,
Flags: 4,
Message: fmt.Sprintf(`%d`, token.RNG.Intn(100)+1),
SenderName: author,
}
bf := byteframe.NewByteFrame()
bf.SetLE()
m.Build(bf)
realPayload = bf.Data()
} else {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.SetLE()
@@ -428,8 +513,8 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage:
if isDiceCommand {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
if returnToSender {
s.stage.BroadcastMHF(resp, nil)
} else {
s.stage.BroadcastMHF(resp, s)
}

View File

@@ -25,7 +25,7 @@ const (
pGardenData // +68
pWeaponType // +1
pWeaponID // +2
pHRP // +2
pHR // +2
pGRP // +4
pKQF // +8
lBookshelfData
@@ -47,7 +47,7 @@ type CharacterSaveData struct {
GardenData []byte
WeaponType uint8
WeaponID uint16
HRP uint16
HR uint16
GR uint16
KQF []byte
@@ -63,7 +63,7 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 128789
pointers[pHouseTier] = 129900
pointers[pToreData] = 130228
pointers[pHRP] = 130550
pointers[pHR] = 130550
pointers[pGRP] = 130556
pointers[pHouseData] = 130561
pointers[pBookshelfData] = 139928
@@ -78,10 +78,10 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 92789
pointers[pHouseTier] = 93900
pointers[pToreData] = 94228
pointers[pHRP] = 94550
pointers[pHR] = 94550
pointers[pGRP] = 94556
pointers[pHouseData] = 94561
pointers[pBookshelfData] = 103928
pointers[pBookshelfData] = 89118 // TODO: fix bookshelf data pointer
pointers[pGalleryData] = 104064
pointers[pGardenData] = 106424
pointers[pRP] = 106614
@@ -91,12 +91,23 @@ func getPointers() map[SavePointer]int {
pointers[pWeaponType] = 60789
pointers[pHouseTier] = 61900
pointers[pToreData] = 62228
pointers[pHRP] = 62550
pointers[pHR] = 62550
pointers[pHouseData] = 62561
pointers[pBookshelfData] = 57118 // This pointer only half works
pointers[pBookshelfData] = 57118 // TODO: fix bookshelf data pointer
pointers[pGalleryData] = 72064
pointers[pGardenData] = 74424
pointers[pRP] = 74614
case _config.S6:
pointers[pWeaponID] = 12522
pointers[pWeaponType] = 12789
pointers[pHouseTier] = 13900
pointers[pToreData] = 14228
pointers[pHR] = 14550
pointers[pHouseData] = 14561
pointers[pBookshelfData] = 9118 // TODO: fix bookshelf data pointer
pointers[pGalleryData] = 24064
pointers[pGardenData] = 26424
pointers[pRP] = 26614
}
if _config.ErupeConfig.RealClientMode == _config.G5 {
pointers[lBookshelfData] = 5548
@@ -163,8 +174,8 @@ func (save *CharacterSaveData) Save(s *Session) {
save.compSave = save.decompSave
}
_, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hrp=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
`, save.compSave, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
_, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hr=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
`, save.compSave, save.HR, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
if err != nil {
s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID))
}
@@ -212,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.Gender = false
}
if !save.IsNewCharacter {
if _config.ErupeConfig.RealClientMode >= _config.F4 {
if _config.ErupeConfig.RealClientMode >= _config.S6 {
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2])
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]
@@ -222,9 +233,9 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2])
save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2])
if _config.ErupeConfig.RealClientMode >= _config.G1 {
if save.HRP == uint16(999) {
if save.HR == uint16(999) {
save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])))
}
}

Some files were not shown because too many files have changed in this diff Show More