Files
Erupe/config/config.go
Houmgaor 279d8b4aa0 feat(config): add RealClientMode infrastructure for multi-version support
Add client version mode support to enable version-specific behavior:

- Add Mode type with constants for all game versions (S1.0 through ZZ)
- Add ClientMode (string) and RealClientMode (Mode) to Config
- Add ClanMemberLimits to GameplayOptions for configurable clan sizes
- Add MaximumFP to GameplayOptions for festa points cap
- Parse ClientMode string to RealClientMode enum in LoadConfig
- Set sensible defaults (ZZ for mode, standard limits for clans)
- Update config.example.json with new fields

This enables cherry-picking version-specific fixes from main branch.
2026-01-30 01:08:37 +01:00

338 lines
8.5 KiB
Go

package config
import (
"fmt"
"log"
"net"
"os"
"strings"
"time"
"github.com/spf13/viper"
)
// Mode represents the client version/mode
type Mode int
// Client version constants
const (
S1 Mode = iota + 1
S15
S2
S25
S3
S35
S4
S5
S55
S6
S7
S8
S85
S9
S10
F1
F2
F3
F4
F5
G1
G2
G3
G31
G32
GG
G5
G51
G52
G6
G61
G7
G8
G81
G9
G91
G10
G101
Z1
Z2
ZZ
)
var versionStrings = []string{
"S1.0", "S1.5", "S2.0", "S2.5", "S3.0", "S3.5", "S4.0", "S5.0", "S5.5", "S6.0",
"S7.0", "S8.0", "S8.5", "S9.0", "S10", "FW.1", "FW.2", "FW.3", "FW.4", "FW.5",
"G1", "G2", "G3", "G3.1", "G3.2", "GG", "G5", "G5.1", "G5.2", "G6", "G6.1",
"G7", "G8", "G8.1", "G9", "G9.1", "G10", "G10.1", "Z1", "Z2", "ZZ",
}
func (m Mode) String() string {
if m < 1 || int(m) > len(versionStrings) {
return "Unknown"
}
return versionStrings[m-1]
}
// Config holds the global server-wide config.
type Config struct {
Host string `mapstructure:"Host"`
BinPath string `mapstructure:"BinPath"`
Language string
DisableSoftCrash bool // Disables the 'Press Return to exit' dialog allowing scripts to reboot the server automatically
HideLoginNotice bool // Hide the Erupe notice on login
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
DevMode bool
ClientMode string // Client version string (e.g., "ZZ", "G10", "S6.0")
RealClientMode Mode // Parsed client mode for version checks
DevModeOptions DevModeOptions
GameplayOptions GameplayOptions
Logging Logging
Discord Discord
Commands []Command
Courses []Course
Database Database
Sign Sign
SignV2 SignV2
Channel Channel
Entrance Entrance
}
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
type DevModeOptions struct {
AutoCreateAccount bool // Automatically create accounts if they don't exist
CleanDB bool // Automatically wipes the DB on server reset.
MaxLauncherHR bool // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
LogInboundMessages bool // Log all messages sent to the server
LogOutboundMessages bool // Log all messages sent to the clients
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
MezFesEvent bool // MezFes status
MezFesAlt bool // Swaps out Volpakkun for Tokotoko
DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
QuestDebugTools bool // Enable various quest debug logs
SaveDumps SaveDumpOptions
}
type SaveDumpOptions struct {
Enabled bool
OutputDir string
}
// GameplayOptions has various gameplay modifiers
type GameplayOptions struct {
FeaturedWeapons int // 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 Festa Points held by a player
DisableLoginBoost bool // Disables the Login Boost system
DisableBoostTime bool // Disables the daily NetCafe Boost Time
BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for
ClanMealDuration int // Seconds that a Clan Meal can be activated for after cooking
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
}
// Logging holds the logging configuration.
type Logging struct {
LogToFile bool // Enable/disable file logging (default: true)
LogFilePath string // File path for logs (default: "logs/erupe.log")
LogMaxSize int // Max size in MB before rotation (default: 100)
LogMaxBackups int // Number of old log files to keep (default: 3)
LogMaxAge int // Max days to retain old logs (default: 28)
LogCompress bool // Compress rotated logs (default: true)
}
// Discord holds the discord integration config.
type Discord struct {
Enabled bool
BotToken string
RealtimeChannelID string
}
// Command is a channelserver chat command
type Command struct {
Name string
Enabled bool
Prefix string
}
// Course represents a course within MHF
type Course struct {
Name string
Enabled bool
}
// Database holds the postgres database config.
type Database struct {
Host string
Port int
User string
Password string
Database string
}
// Sign holds the sign server config.
type Sign struct {
Enabled bool
Port int
}
// SignV2 holds the new sign server config
type SignV2 struct {
Enabled bool
Port int
}
type Channel struct {
Enabled bool
}
// 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
func init() {
// Skip config loading during tests
if isTestMode() {
return
}
var err error
ErupeConfig, err = LoadConfig()
if err != nil {
preventClose(fmt.Sprintf("Failed to load config: %s", err.Error()))
}
}
func isTestMode() bool {
// Check if we're running in test mode
for _, arg := range os.Args {
if strings.HasPrefix(arg, "-test.") {
return true
}
}
return false
}
// getOutboundIP4 gets the preferred outbound ip4 of this machine
// From https://stackoverflow.com/a/37382208
func getOutboundIP4() net.IP {
conn, err := net.Dial("udp4", "8.8.8.8:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.To4()
}
// LoadConfig loads the given config toml file.
func LoadConfig() (*Config, error) {
viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.SetDefault("DevModeOptions.SaveDumps", SaveDumpOptions{
Enabled: false,
OutputDir: "savedata",
})
viper.SetDefault("Logging", Logging{
LogToFile: true,
LogFilePath: "logs/erupe.log",
LogMaxSize: 100,
LogMaxBackups: 3,
LogMaxAge: 28,
LogCompress: true,
})
err := viper.ReadInConfig()
if err != nil {
return nil, err
}
c := &Config{}
err = viper.Unmarshal(c)
if err != nil {
return nil, err
}
if c.Host == "" {
c.Host = getOutboundIP4().To4().String()
}
// Parse ClientMode string to RealClientMode
c.RealClientMode = ZZ // Default to ZZ
if c.ClientMode != "" {
clientModeUpper := strings.ToUpper(c.ClientMode)
for i, vs := range versionStrings {
if clientModeUpper == vs {
c.RealClientMode = Mode(i + 1)
c.ClientMode = vs // Normalize the string
break
}
}
}
// Set default ClanMemberLimits if not configured
if len(c.GameplayOptions.ClanMemberLimits) == 0 {
c.GameplayOptions.ClanMemberLimits = [][]uint8{{0, 30}, {3, 40}, {7, 50}, {10, 60}}
}
return c, nil
}
func preventClose(text string) {
if ErupeConfig != nil && ErupeConfig.DisableSoftCrash {
os.Exit(0)
}
fmt.Println("\nFailed to start Erupe:\n" + text)
go wait()
fmt.Println("\nPress Enter/Return to exit...")
fmt.Scanln()
os.Exit(0)
}
func wait() {
for {
time.Sleep(time.Millisecond * 100)
}
}