docs: add doc.go files and godoc comments to all packages

Add package-level documentation (doc.go) to all 22 first-party
packages and godoc comments to ~150 previously undocumented
exported symbols across common/, network/, and server/.
This commit is contained in:
Houmgaor
2026-02-18 21:39:13 +01:00
parent b9cb274ced
commit 2bd5f98f32
81 changed files with 342 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ import (
"go.uber.org/zap"
)
// Config holds the dependencies required to initialize an APIServer.
type Config struct {
Logger *zap.Logger
DB *sqlx.DB

5
server/api/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package api provides an HTTP REST API server (port 8080) for the V2
// sign/patch flow and administrative endpoints. It handles user
// authentication, character management, launcher configuration, and
// screenshot uploads via JSON and XML over HTTP.
package api

View File

@@ -24,23 +24,30 @@ import (
"golang.org/x/crypto/bcrypt"
)
// Notification type constants for launcher messages.
const (
// NotificationDefault represents a standard notification.
NotificationDefault = iota
// NotificationNew represents a new/unread notification.
NotificationNew
)
// LauncherResponse is the JSON payload returned by the /launcher endpoint,
// containing banners, messages, and links for the game launcher UI.
type LauncherResponse struct {
Banners []_config.APISignBanner `json:"banners"`
Messages []_config.APISignMessage `json:"messages"`
Links []_config.APISignLink `json:"links"`
}
// User represents an authenticated user's session credentials and permissions.
type User struct {
TokenID uint32 `json:"tokenId"`
Token string `json:"token"`
Rights uint32 `json:"rights"`
}
// Character represents a player character's summary data as returned by the API.
type Character struct {
ID uint32 `json:"id"`
Name string `json:"name"`
@@ -51,6 +58,7 @@ type Character struct {
LastLogin int32 `json:"lastLogin" db:"last_login"`
}
// MezFes represents the current Mezeporta Festival event schedule and ticket configuration.
type MezFes struct {
ID uint32 `json:"id"`
Start uint32 `json:"start"`
@@ -60,6 +68,8 @@ type MezFes struct {
Stalls []uint32 `json:"stalls"`
}
// AuthData is the JSON payload returned after successful login or registration,
// containing session info, character list, event data, and server notices.
type AuthData struct {
CurrentTS uint32 `json:"currentTs"`
ExpiryTS uint32 `json:"expiryTs"`
@@ -71,6 +81,7 @@ type AuthData struct {
PatchServer string `json:"patchServer"`
}
// ExportData wraps a character's full database row for save export.
type ExportData struct {
Character map[string]interface{} `json:"character"`
}
@@ -112,6 +123,7 @@ func (s *APIServer) newAuthData(userID uint32, userRights uint32, userTokenID ui
return resp
}
// Launcher handles GET /launcher and returns banners, messages, and links for the launcher UI.
func (s *APIServer) Launcher(w http.ResponseWriter, r *http.Request) {
var respData LauncherResponse
respData.Banners = s.erupeConfig.API.Banners
@@ -121,6 +133,8 @@ func (s *APIServer) Launcher(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(respData)
}
// Login handles POST /login, authenticating a user by username and password
// and returning a session token with character data.
func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
@@ -173,6 +187,8 @@ func (s *APIServer) Login(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(respData)
}
// Register handles POST /register, creating a new user account and returning
// a session token.
func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
@@ -213,6 +229,8 @@ func (s *APIServer) Register(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(respData)
}
// CreateCharacter handles POST /character/create, creating a new character
// slot for the authenticated user.
func (s *APIServer) CreateCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
@@ -242,6 +260,8 @@ func (s *APIServer) CreateCharacter(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(character)
}
// DeleteCharacter handles POST /character/delete, soft-deleting an existing
// character or removing an unfinished one.
func (s *APIServer) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
@@ -267,6 +287,8 @@ func (s *APIServer) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(struct{}{})
}
// ExportSave handles POST /character/export, returning the full character
// database row as JSON for backup purposes.
func (s *APIServer) ExportSave(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
@@ -295,6 +317,8 @@ func (s *APIServer) ExportSave(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(save)
}
// ScreenShotGet handles GET /api/ss/bbs/{id}, serving a previously uploaded
// screenshot image by its token ID.
func (s *APIServer) ScreenShotGet(w http.ResponseWriter, r *http.Request) {
// Get the 'id' parameter from the URL
token := mux.Vars(r)["id"]
@@ -329,6 +353,8 @@ func (s *APIServer) ScreenShotGet(w http.ResponseWriter, r *http.Request) {
}
}
}
// ScreenShot handles POST /api/ss/bbs/upload.php, accepting a JPEG image
// upload from the game client and saving it to the configured output directory.
func (s *APIServer) ScreenShot(w http.ResponseWriter, r *http.Request) {
type Result struct {
XMLName xml.Name `xml:"result"`

View File

@@ -0,0 +1,3 @@
// Package deltacomp implements delta-diff decompression for incremental save
// data updates sent by the MHF client.
package deltacomp

View File

@@ -0,0 +1,4 @@
// Package nullcomp implements null-byte run-length compression used by the MHF
// client for save data. The format uses a "cmp 20110113" header and encodes
// runs of zero bytes as a (0x00, count) pair.
package nullcomp

View File

@@ -0,0 +1,11 @@
// Package channelserver implements the gameplay channel server (TCP port
// 54001+) that handles all in-game multiplayer functionality. It manages
// player sessions, stage (lobby/quest room) state, guild operations, item
// management, event systems, and binary state relay between clients.
//
// Packet handlers are organized by game system into separate files
// (handlers_quest.go, handlers_guild.go, etc.) and registered in
// handlers_table.go. Each handler has the signature:
//
// func(s *Session, p mhfpacket.MHFPacket)
package channelserver

View File

@@ -15,6 +15,7 @@ import (
"go.uber.org/zap"
)
// FestivalColor is a festival color identifier string.
type FestivalColor string
const (
@@ -23,12 +24,14 @@ const (
FestivalColorRed FestivalColor = "red"
)
// FestivalColorCodes maps festival colors to their numeric codes.
var FestivalColorCodes = map[FestivalColor]int16{
FestivalColorNone: -1,
FestivalColorBlue: 0,
FestivalColorRed: 1,
}
// GuildApplicationType is the type of a guild application (applied or invited).
type GuildApplicationType string
const (
@@ -36,6 +39,7 @@ const (
GuildApplicationTypeInvited GuildApplicationType = "invited"
)
// Guild represents a guild with all its metadata.
type Guild struct {
ID uint32 `db:"id"`
Name string `db:"name"`
@@ -64,11 +68,13 @@ type Guild struct {
GuildLeader
}
// GuildLeader holds the character ID and name of a guild's leader.
type GuildLeader struct {
LeaderCharID uint32 `db:"leader_id"`
LeaderName string `db:"leader_name"`
}
// GuildIconPart represents one graphical part of a guild icon.
type GuildIconPart struct {
Index uint16
ID uint16
@@ -82,6 +88,7 @@ type GuildIconPart struct {
PosY uint16
}
// GuildApplication represents a pending guild application or invitation.
type GuildApplication struct {
ID int `db:"id"`
GuildID uint32 `db:"guild_id"`
@@ -91,6 +98,7 @@ type GuildApplication struct {
CreatedAt time.Time `db:"created_at"`
}
// GuildIcon is a composite guild icon made up of multiple parts.
type GuildIcon struct {
Parts []GuildIconPart
}
@@ -467,6 +475,7 @@ func (guild *Guild) HasApplicationForCharID(s *Session, charID uint32) (bool, er
return true, nil
}
// CreateGuild creates a new guild in the database and adds the session's character as its leader.
func CreateGuild(s *Session, guildName string) (int32, error) {
transaction, err := s.server.db.Begin()
@@ -539,6 +548,7 @@ func rollbackTransaction(s *Session, transaction *sql.Tx) {
}
}
// GetGuildInfoByID retrieves guild info by guild ID, returning nil if not found.
func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
@@ -562,6 +572,7 @@ func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) {
return buildGuildObjectFromDbResult(rows, err, s)
}
// GetGuildInfoByCharacterId retrieves guild info for a character, including applied guilds.
func GetGuildInfoByCharacterId(s *Session, charID uint32) (*Guild, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s

View File

@@ -32,6 +32,7 @@ var achievementCurveMap = map[uint8][]int32{
32: achievementCurves[3],
}
// Achievement represents computed achievement data for a character.
type Achievement struct {
Level uint8
Value uint32
@@ -42,6 +43,7 @@ type Achievement struct {
Trophy uint8
}
// GetAchData computes achievement level and progress from a raw score.
func GetAchData(id uint8, score int32) Achievement {
curve := achievementCurveMap[id]
var ach Achievement

View File

@@ -109,6 +109,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// CafeBonus represents a cafe duration bonus reward entry.
type CafeBonus struct {
ID uint32 `db:"id"`
TimeReq uint32 `db:"time_req"`

View File

@@ -9,6 +9,7 @@ import (
"time"
)
// CampaignEvent represents a promotional campaign event.
type CampaignEvent struct {
ID uint32
Unk0 uint32
@@ -35,6 +36,7 @@ type CampaignEvent struct {
Categories []uint16
}
// CampaignCategory represents a category grouping for campaign events.
type CampaignCategory struct {
ID uint16
Type uint8
@@ -42,6 +44,7 @@ type CampaignCategory struct {
Description string
}
// CampaignLink links a campaign event to its items/rewards.
type CampaignLink struct {
CategoryID uint16
CampaignID uint32

View File

@@ -7,6 +7,7 @@ import (
"time"
)
// RyoudamaReward represents a caravan (Ryoudama) reward entry.
type RyoudamaReward struct {
Unk0 uint8
Unk1 uint8
@@ -16,22 +17,26 @@ type RyoudamaReward struct {
Unk5 uint16
}
// RyoudamaKeyScore represents a caravan key score entry.
type RyoudamaKeyScore struct {
Unk0 uint8
Unk1 int32
}
// RyoudamaCharInfo represents per-character caravan info.
type RyoudamaCharInfo struct {
CID uint32
Unk0 int32
Name string
}
// RyoudamaBoostInfo represents caravan boost status.
type RyoudamaBoostInfo struct {
Start time.Time
End time.Time
}
// Ryoudama represents complete caravan data.
type Ryoudama struct {
Reward []RyoudamaReward
KeyScore []RyoudamaKeyScore

View File

@@ -9,6 +9,7 @@ import (
"go.uber.org/zap"
)
// GetCharacterSaveData loads a character's save data from the database.
func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) {
result, err := s.server.db.Query("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID)
if err != nil {

View File

@@ -10,11 +10,13 @@ import (
"go.uber.org/zap"
)
// PaperMissionTimetable represents a daily mission schedule entry.
type PaperMissionTimetable struct {
Start time.Time
End time.Time
}
// PaperMissionData represents daily mission details.
type PaperMissionData struct {
Unk0 uint8
Unk1 uint8
@@ -25,11 +27,13 @@ type PaperMissionData struct {
Reward2Quantity uint8
}
// PaperMission represents a daily mission wrapper.
type PaperMission struct {
Timetables []PaperMissionTimetable
Data []PaperMissionData
}
// PaperData represents complete daily paper data.
type PaperData struct {
Unk0 uint16
Unk1 int16
@@ -40,6 +44,7 @@ type PaperData struct {
Unk6 int16
}
// PaperGift represents a paper gift reward entry.
type PaperGift struct {
Unk0 uint16
Unk1 uint8

View File

@@ -10,6 +10,7 @@ import (
"go.uber.org/zap"
)
// Distribution represents an item distribution event.
type Distribution struct {
ID uint32 `db:"id"`
Deadline time.Time `db:"deadline"`
@@ -119,6 +120,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// DistributionItem represents a single item in a distribution.
type DistributionItem struct {
ItemType uint8 `db:"item_type"`
ID uint32 `db:"id"`

View File

@@ -11,6 +11,7 @@ import (
"go.uber.org/zap"
)
// Event represents an in-game event entry.
type Event struct {
EventType uint16
Unk1 uint16

View File

@@ -8,6 +8,7 @@ import (
"go.uber.org/zap"
)
// Gacha represents a gacha lottery definition.
type Gacha struct {
ID uint32 `db:"id"`
MinGR uint32 `db:"min_gr"`
@@ -22,6 +23,7 @@ type Gacha struct {
Hidden bool `db:"hidden"`
}
// GachaEntry represents a gacha entry (step/box).
type GachaEntry struct {
EntryType uint8 `db:"entry_type"`
ID uint32 `db:"id"`
@@ -36,6 +38,7 @@ type GachaEntry struct {
Name string `db:"name"`
}
// GachaItem represents a single item in a gacha pool.
type GachaItem struct {
ItemType uint8 `db:"item_type"`
ItemID uint16 `db:"item_id"`

View File

@@ -9,6 +9,7 @@ import (
"go.uber.org/zap"
)
// GuildAdventure represents a guild adventure expedition.
type GuildAdventure struct {
ID uint32 `db:"id"`
Destination uint32 `db:"destination"`

View File

@@ -28,6 +28,7 @@ END
FROM guild_alliances ga
`
// GuildAlliance represents a multi-guild alliance.
type GuildAlliance struct {
ID uint32 `db:"id"`
Name string `db:"name"`
@@ -43,6 +44,7 @@ type GuildAlliance struct {
SubGuild2 Guild
}
// GetAllianceData loads alliance data from the database.
func GetAllianceData(s *Session, AllianceID uint32) (*GuildAlliance, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s

View File

@@ -10,6 +10,7 @@ import (
"go.uber.org/zap"
)
// MessageBoardPost represents a guild message board post.
type MessageBoardPost struct {
ID uint32 `db:"id"`
StampID uint32 `db:"stamp_id"`

View File

@@ -8,6 +8,7 @@ import (
"go.uber.org/zap"
)
// GuildMeal represents a guild cooking meal entry.
type GuildMeal struct {
ID uint32 `db:"id"`
MealID uint32 `db:"meal_id"`

View File

@@ -8,6 +8,7 @@ import (
"go.uber.org/zap"
)
// GuildMember represents a guild member with role and stats.
type GuildMember struct {
GuildID uint32 `db:"guild_id"`
CharID uint32 `db:"character_id"`
@@ -92,6 +93,7 @@ SELECT
LEFT JOIN guilds g ON g.id = gc.guild_id
`
// GetGuildMembers loads all members of a guild.
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
@@ -120,6 +122,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe
return members, nil
}
// GetCharacterGuildData loads a character's guild membership.
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)

View File

@@ -5,6 +5,7 @@ import (
"erupe-ce/network/mhfpacket"
)
// GuildMission represents a guild mission entry.
type GuildMission struct {
ID uint32
Unk uint32

View File

@@ -9,6 +9,7 @@ import (
"go.uber.org/zap"
)
// TreasureHunt represents a guild treasure hunt entry.
type TreasureHunt struct {
HuntID uint32 `db:"id"`
HostID uint32 `db:"host_id"`
@@ -147,6 +148,7 @@ func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// TreasureSouvenir represents a guild treasure souvenir entry.
type TreasureSouvenir struct {
Destination uint32
Quantity uint32

View File

@@ -47,6 +47,7 @@ func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// HouseData represents player house/my house data.
type HouseData struct {
CharID uint32 `db:"id"`
HR uint16 `db:"hr"`
@@ -329,6 +330,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// Title represents a hunter title entry.
type Title struct {
ID uint16 `db:"id"`
Acquired time.Time `db:"unlocked_at"`

View File

@@ -11,6 +11,7 @@ import (
"go.uber.org/zap"
)
// Mail represents an in-game mail message.
type Mail struct {
ID int `db:"id"`
SenderID uint32 `db:"sender_id"`
@@ -79,6 +80,7 @@ func (m *Mail) MarkRead(s *Session) error {
return nil
}
// GetMailListForCharacter loads all mail for a character.
func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
rows, err := s.server.db.Queryx(`
SELECT
@@ -127,6 +129,7 @@ func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
return allMail, nil
}
// GetMailByID loads a single mail by ID.
func GetMailByID(s *Session, ID int) (*Mail, error) {
row := s.server.db.QueryRowx(`
SELECT
@@ -167,6 +170,7 @@ func GetMailByID(s *Session, ID int) (*Mail, error) {
return mail, nil
}
// SendMailNotification sends a new mail notification to a player.
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
bf := byteframe.NewByteFrame()

View File

@@ -370,6 +370,7 @@ func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
// Airou represents Airou (felyne companion) data.
type Airou struct {
ID uint32
Name []byte
@@ -445,6 +446,7 @@ func getGuildAirouList(s *Session) []Airou {
return guildCats
}
// GetAirouDetails parses Airou data from a ByteFrame.
func GetAirouDetails(bf *byteframe.ByteFrame) []Airou {
catCount := bf.ReadUint8()
cats := make([]Airou, catCount)

View File

@@ -241,6 +241,7 @@ func handleMsgMhfGetLobbyCrowd(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x320))
}
// TrendWeapon represents trending weapon usage data.
type TrendWeapon struct {
WeaponType uint8
WeaponID uint16

View File

@@ -48,6 +48,7 @@ func equal(a, b []byte) bool {
return true
}
// BackportQuest converts a quest binary to an older format.
func BackportQuest(data []byte) []byte {
wp := binary.LittleEndian.Uint32(data[0:4]) + 96
rp := wp + 4

View File

@@ -48,6 +48,7 @@ func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
})
}
// RaviUpdate represents a Raviente register update entry.
type RaviUpdate struct {
Op uint8
Dest uint8

View File

@@ -107,6 +107,7 @@ const rengokuScoreQuery = `, c.name FROM rengoku_score rs
LEFT JOIN characters c ON c.id = rs.character_id
LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id `
// RengokuScore represents a Rengoku (Hunting Road) ranking score.
type RengokuScore struct {
Name string `db:"name"`
Score uint32 `db:"score"`

View File

@@ -7,6 +7,7 @@ import (
"go.uber.org/zap"
)
// Scenario represents scenario counter data.
type Scenario struct {
MainID uint32
// 0 = Basic

View File

@@ -6,27 +6,32 @@ import (
"time"
)
// SeibattleTimetable represents a seibattle schedule entry.
type SeibattleTimetable struct {
Start time.Time
End time.Time
}
// SeibattleKeyScore represents a seibattle key score.
type SeibattleKeyScore struct {
Unk0 uint8
Unk1 int32
}
// SeibattleCareer represents seibattle career stats.
type SeibattleCareer struct {
Unk0 uint16
Unk1 uint16
Unk2 uint16
}
// SeibattleOpponent represents seibattle opponent data.
type SeibattleOpponent struct {
Unk0 int32
Unk1 int8
}
// SeibattleConventionResult represents a seibattle convention result.
type SeibattleConventionResult struct {
Unk0 uint32
Unk1 uint16
@@ -35,10 +40,12 @@ type SeibattleConventionResult struct {
Unk4 uint16
}
// SeibattleCharScore represents a seibattle per-character score.
type SeibattleCharScore struct {
Unk0 uint32
}
// SeibattleCurResult represents a seibattle current result.
type SeibattleCurResult struct {
Unk0 uint32
Unk1 uint16
@@ -46,6 +53,7 @@ type SeibattleCurResult struct {
Unk3 uint16
}
// Seibattle represents complete seibattle data.
type Seibattle struct {
Timetable []SeibattleTimetable
KeyScore []SeibattleKeyScore
@@ -159,6 +167,7 @@ func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// WeeklySeibatuRankingReward represents a weekly seibattle ranking reward.
type WeeklySeibatuRankingReward struct {
Unk0 int32
Unk1 int32

View File

@@ -9,6 +9,7 @@ import (
"go.uber.org/zap"
)
// ShopItem represents a shop item listing.
type ShopItem struct {
ID uint32 `db:"id"`
ItemID uint32 `db:"item_id"`
@@ -248,6 +249,7 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
// FPointExchange represents a frontier point exchange entry.
type FPointExchange struct {
ID uint32 `db:"id"`
ItemType uint8 `db:"item_type"`

View File

@@ -7,6 +7,7 @@ import (
"time"
)
// TournamentInfo0 represents tournament information (type 0).
type TournamentInfo0 struct {
ID uint32
MaxPlayers uint32
@@ -28,6 +29,7 @@ type TournamentInfo0 struct {
Unk6 string
}
// TournamentInfo21 represents tournament information (type 21).
type TournamentInfo21 struct {
Unk0 uint32
Unk1 uint32
@@ -35,6 +37,7 @@ type TournamentInfo21 struct {
Unk3 uint8
}
// TournamentInfo22 represents tournament information (type 22).
type TournamentInfo22 struct {
Unk0 uint32
Unk1 uint32
@@ -110,6 +113,7 @@ func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// TournamentReward represents a tournament reward entry.
type TournamentReward struct {
Unk0 uint16
Unk1 uint16

View File

@@ -14,21 +14,25 @@ import (
"erupe-ce/network/mhfpacket"
)
// TowerInfoTRP represents tower RP (points) info.
type TowerInfoTRP struct {
TR int32
TRP int32
}
// TowerInfoSkill represents tower skill info.
type TowerInfoSkill struct {
TSP int32
Skills []int16 // 64
}
// TowerInfoHistory represents tower clear history.
type TowerInfoHistory struct {
Unk0 []int16 // 5
Unk1 []int16 // 5
}
// TowerInfoLevel represents tower level info.
type TowerInfoLevel struct {
Floors int32
Unk1 int32
@@ -36,6 +40,7 @@ type TowerInfoLevel struct {
Unk3 int32
}
// EmptyTowerCSV creates an empty CSV string of the given length.
func EmptyTowerCSV(len int) string {
temp := make([]string, len)
for i := range temp {
@@ -194,6 +199,7 @@ var tenrouiraiData = []TenrouiraiData{
{2, 6, 40, 0, 3, 1, 0, 0, 1, 1},
}
// TenrouiraiProgress represents Tenrouirai (sky corridor) progress.
type TenrouiraiProgress struct {
Page uint8
Mission1 uint16
@@ -201,17 +207,20 @@ type TenrouiraiProgress struct {
Mission3 uint16
}
// TenrouiraiReward represents a Tenrouirai reward.
type TenrouiraiReward struct {
Index uint8
Item []uint16 // 5
Quantity []uint8 // 5
}
// TenrouiraiKeyScore represents a Tenrouirai key score.
type TenrouiraiKeyScore struct {
Unk0 uint8
Unk1 int32
}
// TenrouiraiData represents Tenrouirai data.
type TenrouiraiData struct {
Block uint8
Mission uint8
@@ -231,17 +240,20 @@ type TenrouiraiData struct {
Skill6 uint8 // 50
}
// TenrouiraiCharScore represents a Tenrouirai per-character score.
type TenrouiraiCharScore struct {
Score int32
Name string
}
// TenrouiraiTicket represents a Tenrouirai ticket entry.
type TenrouiraiTicket struct {
Unk0 uint8
RP uint32
Unk2 uint32
}
// Tenrouirai represents complete Tenrouirai data.
type Tenrouirai struct {
Progress []TenrouiraiProgress
Reward []TenrouiraiReward
@@ -429,11 +441,13 @@ func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {
doAckEarthSucceed(s, pkt.AckHandle, data)
}
// GemInfo represents gem (decoration) info.
type GemInfo struct {
Gem uint16
Quantity uint16
}
// GemHistory represents gem usage history.
type GemHistory struct {
Gem uint16
Message uint16

View File

@@ -9,6 +9,7 @@ import (
"erupe-ce/server/channelserver/compression/nullcomp"
)
// SavePointer identifies a section within the character save data blob.
type SavePointer int
const (
@@ -29,6 +30,7 @@ const (
lBookshelfData
)
// CharacterSaveData holds a character's save data and its parsed fields.
type CharacterSaveData struct {
CharID uint32
Name string

View File

@@ -11,6 +11,7 @@ import (
"go.uber.org/zap"
)
// Raviente holds shared state for the Raviente siege event.
type Raviente struct {
sync.Mutex
id uint16

View File

@@ -264,6 +264,7 @@ func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session)
}
}
// WorldcastMHF broadcasts a packet to all sessions across all channel servers.
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) {
for _, c := range s.Channels {
if c == ignoredChannel {
@@ -292,6 +293,7 @@ func (s *Server) BroadcastChatMessage(message string) {
}, nil)
}
// DiscordChannelSend sends a chat message to the configured Discord channel.
func (s *Server) DiscordChannelSend(charName string, content string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
message := fmt.Sprintf("**%s**: %s", charName, content)
@@ -299,6 +301,7 @@ func (s *Server) DiscordChannelSend(charName string, content string) {
}
}
// DiscordScreenShotSend sends a screenshot link to the configured Discord channel.
func (s *Server) DiscordScreenShotSend(charName string, title string, description string, articleToken string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
imageUrl := fmt.Sprintf("%s:%d/api/ss/bbs/%s", s.erupeConfig.Screenshots.Host, s.erupeConfig.Screenshots.Port, articleToken)
@@ -307,6 +310,7 @@ func (s *Server) DiscordScreenShotSend(charName string, title string, descriptio
}
}
// FindSessionByCharID looks up a session by character ID across all channels.
func (s *Server) FindSessionByCharID(charID uint32) *Session {
for _, c := range s.Channels {
for _, session := range c.sessions {
@@ -318,6 +322,7 @@ func (s *Server) FindSessionByCharID(charID uint32) *Session {
return nil
}
// DisconnectUser disconnects all sessions belonging to the given user ID.
func (s *Server) DisconnectUser(uid uint32) {
var cid uint32
var cids []uint32
@@ -343,6 +348,7 @@ func (s *Server) DisconnectUser(uid uint32) {
}
}
// FindObjectByChar finds a stage object owned by the given character ID.
func (s *Server) FindObjectByChar(charID uint32) *Object {
s.stagesLock.RLock()
defer s.stagesLock.RUnlock()
@@ -361,6 +367,7 @@ func (s *Server) FindObjectByChar(charID uint32) *Object {
return nil
}
// HasSemaphore checks if the given session is hosting any semaphore.
func (s *Server) HasSemaphore(ses *Session) bool {
for _, semaphore := range s.semaphore {
if semaphore.host == ses {
@@ -370,6 +377,7 @@ func (s *Server) HasSemaphore(ses *Session) bool {
return false
}
// Season returns the current in-game season (0-2) based on server ID and time.
func (s *Server) Season() uint8 {
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)

View File

@@ -323,6 +323,7 @@ func (s *Session) getObjectId() uint32 {
return uint32(s.objectID)<<16 | uint32(s.objectIndex)
}
// GetSemaphoreID returns the semaphore ID held by the session, varying by semaphore mode.
func (s *Session) GetSemaphoreID() uint32 {
if s.semaphoreMode {
return 0x000E0000 + uint32(s.semaphoreID[1])

View File

@@ -5,6 +5,11 @@ import (
"time"
)
// TimeAdjusted, TimeMidnight, TimeWeekStart, TimeWeekNext, and TimeGameAbsolute
// are package-level wrappers around the gametime utility functions, providing
// convenient access to adjusted server time, daily/weekly boundaries, and the
// absolute game timestamp used by the MHF client.
func TimeAdjusted() time.Time { return gametime.Adjusted() }
func TimeMidnight() time.Time { return gametime.Midnight() }
func TimeWeekStart() time.Time { return gametime.WeekStart() }

View File

@@ -8,6 +8,8 @@ import (
"go.uber.org/zap"
)
// Commands defines the slash commands registered with Discord, including
// account linking and password management.
var Commands = []*discordgo.ApplicationCommand{
{
Name: "link",
@@ -35,6 +37,8 @@ var Commands = []*discordgo.ApplicationCommand{
},
}
// DiscordBot manages a Discord session and provides methods for relaying
// messages between the game server and a configured Discord channel.
type DiscordBot struct {
Session *discordgo.Session
config *_config.Config
@@ -43,11 +47,14 @@ type DiscordBot struct {
RelayChannel *discordgo.Channel
}
// Options holds the configuration and logger required to create a DiscordBot.
type Options struct {
Config *_config.Config
Logger *zap.Logger
}
// NewDiscordBot creates a DiscordBot using the provided options, establishing
// a Discord session and optionally resolving the relay channel.
func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
session, err := discordgo.New("Bot " + options.Config.Discord.BotToken)
@@ -77,6 +84,7 @@ func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
return
}
// Start opens the websocket connection to Discord.
func (bot *DiscordBot) Start() (err error) {
err = bot.Session.Open()
@@ -105,6 +113,8 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
return result
}
// RealtimeChannelSend sends a message to the configured relay channel. If no
// relay channel is configured, the call is a no-op.
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
if bot.RelayChannel == nil {
return
@@ -114,6 +124,8 @@ func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
return
}
// ReplaceTextAll replaces every match of regex in text by calling handler with
// the first capture group of each match and substituting the result.
func ReplaceTextAll(text string, regex *regexp.Regexp, handler func(input string) string) string {
result := regex.ReplaceAllFunc([]byte(text), func(s []byte) []byte {
input := regex.ReplaceAllString(string(s), `$1`)

4
server/discordbot/doc.go Normal file
View File

@@ -0,0 +1,4 @@
// Package discordbot provides an optional Discord bot integration that relays
// in-game chat to Discord channels and supports slash commands for server
// management.
package discordbot

View File

@@ -0,0 +1,12 @@
// Package entranceserver implements the MHF entrance server, which listens on
// TCP port 53310 and acts as the gateway between authentication (sign server)
// and gameplay (channel servers). It presents the server list to authenticated
// clients, handles character selection, and directs players to the appropriate
// channel server.
//
// The entrance server uses MHF's custom "binary8" encryption and "sum32"
// checksum for all client-server communication. Each client connection is
// short-lived: the server sends a single response containing the server list
// (SV2/SVR) and optionally user session data (USR), then closes the
// connection.
package entranceserver

5
server/signserver/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package signserver implements the MHF sign server, which handles client
// authentication, session creation, and character management. It listens
// on TCP port 53312 and is the first server a client connects to in the
// three-server network model (sign, entrance, channel).
package signserver

View File

@@ -1,7 +1,11 @@
package signserver
// RespID represents a sign server response code sent to the client
// to indicate the result of an authentication or session operation.
type RespID uint8
// Sign server response codes. These values are sent as the first byte of
// a sign response and map to client-side error messages.
const (
SIGN_UNKNOWN RespID = iota
SIGN_SUCCESS