Skip to content

Commit

Permalink
feat: add client repacking for launcher and rewrite configurable edit (
Browse files Browse the repository at this point in the history
…#9)

* feat: config

* config: local

* feat: repack

* feat: repack

* fix: use config properly

* fix: leniency

* feat: cross platform repacking

* fix: bad mac paths

* docs: add example config.toml

* fix: makefile for mac

* docs: update readme

* update readme

* change windows examples to backslash

* Update edit/edit.go
  • Loading branch information
luan authored Jul 20, 2023
1 parent 3b39ad2 commit f0a0c66
Show file tree
Hide file tree
Showing 10 changed files with 1,194 additions and 171 deletions.
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, 0xb4, 0x75, 0x0e, 0xe8, 0xb5, 0x6c}
var removeBattleyeHex = []byte{0x8d, 0x4d, 0xb4, 0xeb, 0x0e, 0xe8, 0xb5, 0x6c}

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

0 comments on commit f0a0c66

Please sign in to comment.