Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add client repacking for launcher and rewrite configurable edit #9

Merged
merged 14 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ build:
GOOS=windows GOARCH=amd64 go build -o client-editor-windows-x64.exe main.go
GOOS=linux GOARCH=386 go build -o client-editor-linux-x86 main.go
GOOS=linux GOARCH=amd64 go build -o client-editor-linux-x64 main.go
GOOS=darwin GOARCH=386 go build -o client-editor-darwin-x86 main.go
GOOS=darwin GOARCH=amd64 go build -o client-editor-darwin-x64 main.go
GOOS=darwin GOARCH=arm64 go build -o client-editor-darwin-arm64 main.go
zip client-editor-windows.zip client-editor-windows-* *.key
zip client-editor-linux.zip client-editor-linux-* *.key
zip client-editor-darwin.zip client-editor-darwin-* *.key
Expand Down
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
# client-editor

### Usage
## Usage

### Edit client

Edit or make a `config.toml` with all the URLs you want to change, there's an example one in `config.toml.dist`.

```bash
./client-editor.exe <tibia.exe location> <new custom login webservice>
# Windows
.\client-editor.exe edit -t <tibia.exe location> -c config.toml

# Unix
./client-editor edit -t <tibia.exe location> -c config.toml
```

For a local client using [SlenderAAC](https://github.com/luan/slenderaac) you can use `local.toml` as a base.

```bash
# Windows
.\client-editor.exe edit -t <tibia.exe location> -c local.toml

# Unix
./client-editor edit -t <tibia.exe location> -c local.toml
```

### Compiled Releases (Windows/Linux)
### Repack client

Repack an existing tibia client for [use with slender-launcher](https://github.com/luan/slender-launcher). Repack requires a `client.<platform>.json` and `assets.<platform>.json` for each of the platforms you want to repack. Check out https://github.com/luan/tibia-client for an example.

```bash
# Windows
.\client-editor.exe repack -s C:\Games\Tibia-windows -d C:\Users\YourName\src\tibia-client -p windows
.\client-editor.exe repack -s C:\Games\Tibia-mac -d C:\Users\YourName\src\tibia-client -p mac
.\client-editor.exe repack -s C:\Games\Tibia-linux -d C:\Users\YourName\src\tibia-client -p linux

# Unix
./client-editor repack -s ~/Games/Tibia-windows -d ~/src/tibia-client -p windows
./client-editor repack -s ~/Games/Tibia-mac -d ~/src/tibia-client -p mac
./client-editor repack -s ~/Games/Tibia-linux -d ~/src/tibia-client -p linux
```

### Compiled Releases (Windows/Mac/Linux)

https://github.com/opentibiabr/client-editor/releases

### How to Compile

Requirements: golang 1.8+

```bash
Expand Down
14 changes: 14 additions & 0 deletions config.toml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
loginWebService = "https://example.com/api/login"
clientWebService = "https://example.com/api/login"
tibiaPageUrl = "https://example.com/"
tibiaStoreGetCoinsUrl = "https://example.com/shop/coins"
getPremiumUrl = "https://example.com/pages/vip-features"
createAccountUrl = "https://example.com/account/signup"
accessAccountUrl = "https://example.com/account"
lostAccountUrl = "https://example.com/account/lost"
manualUrl = "https://example.com/pages/server-info"
faqUrl = "https://example.com/pages/server-info"
premiumFeaturesUrl = "https://example.com/pages/vip-features"
crashReportUrl = "https://example.com/api/crash-report"
cipSoftUrl = "https://example.com/"
fpsHistoryRecipient = "https://example.com/api/hardware-report"
198 changes: 198 additions & 0 deletions edit/edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package edit

import (
"bytes"
"fmt"
"os"
"path/filepath"
"time"

"github.com/spf13/viper"
)

var (
properties = []string{
"loginWebService",
"clientWebService",
"tibiaPageUrl",
"tibiaStoreGetCoinsUrl",
"getPremiumUrl",
"createAccountUrl",
"accessAccountUrl",
"lostAccountUrl",
"manualUrl",
"faqUrl",
"premiumFeaturesUrl",
"crashReportUrl",
"fpsHistoryRecipient",
"cipSoftUrl",
}
)

var paddingByte = []byte{0x20}
var battleyeHex = []byte{0x8d, 0x4d, 0x80, 0x75, 0x0e, 0xe8, 0x2e, 0xc8}
var removeBattleyeHex = []byte{0x8d, 0x4d, 0x80, 0xeb, 0x0e, 0xe8, 0x2e, 0xc8}
luan marked this conversation as resolved.
Show resolved Hide resolved

func Edit(tibiaExe string) {
err := viper.ReadInConfig()
if err != nil {
fmt.Printf("[ERROR] Failed to read config file: %s\n", err.Error())
os.Exit(1)
}
// Check if all properties are present in the config file
missingProperties := make([]string, 0)
for _, prop := range properties {
if !viper.IsSet(prop) {
missingProperties = append(missingProperties, prop)
}
}

// Error out if any properties are missing
if len(missingProperties) > 0 {
fmt.Printf("[ERROR] Missing properties in the config file: %v\n", missingProperties)
os.Exit(1)
}

configValues := make(map[string]string)
for _, prop := range properties {
value := viper.GetString(prop)
configValues[prop] = value
}

tibiaPath, tibiaBinary := readFile(tibiaExe)
originalBinarySize := len(tibiaBinary)

backupTibiaExecutable(tibiaPath, tibiaBinary)

tibiaBinary = replaceTibiaRSAKey(tibiaBinary)
tibiaBinary = removeBattlEye(tibiaBinary)

for prop, value := range configValues {
ok := setPropertyByName(tibiaBinary, prop, value)
if !ok {
fmt.Printf("[ERROR] Unable to replace %s\n", prop)
}
}

exportModifiedFile(tibiaPath, tibiaBinary, originalBinarySize)
}

func backupTibiaExecutable(tibiaPath string, tibiaBinary []byte) {
tibiaExeFileName := filepath.Base(tibiaPath)
tibiaExeBackupPath := filepath.Join(filepath.Dir(tibiaPath), fmt.Sprintf("BKP%d-%s", time.Now().Unix(), tibiaExeFileName))
tibiaExeBackupFileName := filepath.Base(tibiaExeBackupPath)

fmt.Printf("[INFO] Backing up %s to %s\n", tibiaExeFileName, tibiaExeBackupFileName)

err := os.WriteFile(tibiaExeBackupPath, tibiaBinary, 0644)
if err != nil {
fmt.Printf("[ERROR] %s\n", err.Error())
os.Exit(1)
}
}

func replaceTibiaRSAKey(tibiaBinary []byte) []byte {
tibiaRsaPath := "tibia_rsa.key"
otservRsaPath := "otserv_rsa.key"

_, tibiaRsa := readFile(tibiaRsaPath)
_, otservRsa := readFile(otservRsaPath)

fmt.Printf("[INFO] Searching for Tibia RSA... \n")

if bytes.ContainsAny(tibiaBinary, string(tibiaRsa)) {
fmt.Printf("[INFO] Tibia RSA found!\n")
tibiaBinary = bytes.Replace(tibiaBinary, tibiaRsa, otservRsa, 1)
fmt.Printf("[PATCH] Tibia RSA replaced with OTServ RSA!\n")
} else if bytes.ContainsAny(tibiaBinary, string(otservRsa)) {
fmt.Printf("[WARN] OTServ RSA already patched!\n")
} else {
fmt.Printf("[ERROR] Unable to find Tibia RSA\n")
os.Exit(1)
}

return tibiaBinary
}

func removeBattlEye(tibiaBinary []byte) []byte {
fmt.Printf("[INFO] Searching for Battleye... \n")

if bytes.Contains(tibiaBinary, battleyeHex) {
fmt.Printf("[INFO] Battleye found!\n")
tibiaBinary = bytes.Replace(tibiaBinary, battleyeHex, removeBattleyeHex, 1)
fmt.Printf("[PATCH] Battleye removed!\n")
} else if bytes.Contains(tibiaBinary, removeBattleyeHex) {
fmt.Printf("[WARN] Battleye already removed!\n")
} else {
fmt.Printf("[WARN] Battleye not found\n")
}

return tibiaBinary
}

func exportModifiedFile(tibiaPath string, tibiaBinary []byte, originalBinarySize int) {
outputFilePath := tibiaPath

if len(tibiaBinary) != originalBinarySize {
fmt.Printf("[ERROR] Invalid patched file size, original: %d, modified: %d\n", originalBinarySize, len(tibiaBinary))
os.Exit(1)
}

err := os.WriteFile(outputFilePath, tibiaBinary, 0644)
if err != nil {
fmt.Printf("[ERROR] %s\n", err.Error())
os.Exit(1)
}

fmt.Printf("[INFO] Patched file exported to: %s\n", outputFilePath)
}

func readFile(filePath string) (string, []byte) {
fileData, err := os.ReadFile(filePath)
if err != nil {
fmt.Printf("[ERROR] %s\n", err.Error())
os.Exit(1)
}

return filePath, fileData
}

func setPropertyByName(tibiaBinary []byte, propertyName string, customValue string) bool {
originalBinarySize := len(tibiaBinary)
propertyName = fmt.Sprintf("%s=", propertyName)
propertyIndex := bytes.Index(tibiaBinary, []byte(propertyName))
if propertyIndex != -1 {
// Extract current property value
startValue := propertyIndex + len(propertyName)
endValue := startValue + bytes.IndexByte(tibiaBinary[startValue:], '\n')
propertyValue := string(tibiaBinary[startValue:endValue])

if len(customValue) > len(propertyValue) {
fmt.Printf("[ERROR] Cannot replace %s to '%s' because the new value must be smaller than '%s' (%d chars).\n", propertyName, customValue, propertyValue, len(propertyValue))
return false
}

fmt.Printf("[INFO] %s found! %s\n", propertyName, propertyValue)

// Create the new value with the correct length
customValueBytes := []byte(customValue)
paddedCustomValue := append(customValueBytes, bytes.Repeat(paddingByte, len(propertyValue)-len(customValueBytes))...)

// Merge everything back to the client
remainingBinary := tibiaBinary[endValue:]

tibiaBinary = append(tibiaBinary[:startValue], paddedCustomValue...)
tibiaBinary = append(tibiaBinary, remainingBinary...)

if originalBinarySize != len(tibiaBinary) {
fmt.Printf("[ERROR] Fatal error: The new modified client (size %d) has a different byte size from the original (size %d). Make sure to use the correct versions of both the client and client-editor or report a bug.\n", len(tibiaBinary), originalBinarySize)
os.Exit(1)
}

fmt.Printf("[PATCH] %s replaced to %s!\n", propertyName, customValue)
return true
}

fmt.Printf("[WARNING] %s was not found!\n", propertyName)
return false
}
30 changes: 30 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module github.com/elysiera/client-editor

go 1.19

require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/schollz/progressbar/v3 v3.13.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.16.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading