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

Major code rewrite #8

Merged
merged 2 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
all: build-windows

# Not powershell compatible. Meant to be used from unix shell
build-windows:
env GOOS=windows GOARCH=amd64 go build -o ./build/universal-checksum-patcher-windows.exe *.go
env GOOS=windows GOARCH=amd64 go build -o ./build/universal-checksum-patcher.exe *.go
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Universal Paradox games checksum patcher

This is a lightweight patch, that forces game ignore checksum when starting and loading ironman game.
This is a patch, that forces game ignore checksum when starting and loading ironman game.

In other words, yes, it gives you the ability to get achievements with mods enabled. And not giving you ability to use game console or connect to servers with other checksum.

# Installation

1. Download latest binary of patcher from releases (or build it in case you know what you doing)
2. Unzip it in game directory (rightclick on game on steam > Manage > Browse local files)
3. Run paradox-checksum-patcher.exe or paradox-checksum-patcher
1. Download latest binary of patcher from releases (or build it if you know what you doing)
2. Unzip it in game directory (right click on game on steam > Manage > Browse local files)
3. Run universal-checksum-patcher.exe

Conrats, you done! In case you see unsupported version error most likely Paradox broke something and all you can do is wait until i update patch. Most likely i'll update patch when i decide to play, so feel free to get needed byte code and modify source code (i'll merge your pull request if you decide to do that. Or create fork, i don't care)
Congrats, you're done! In case you see unsupported version error most likely Paradox broke something and all you can do is wait until I update patch. Most likely i'll update patch when I decide to play, so feel free to get needed byte code and modify source code (I'll merge your pull request if you decide to do that. Or create fork, I don't care)

# Supported games and platforms
| | Windows | Linux | MacOS |
|-----------------------|------------------------|------------------------|--------|
| Europa Universalis IV | Yes :heavy_check_mark: | No :x: | No :x: |
| Hearts of Iron IV | Yes :heavy_check_mark: | No :x: | No :x: |
| | Windows | Linux(native) | MacOS |
|-----------------------|------------------------|---------------|--------|
| Europa Universalis IV | Yes :heavy_check_mark: | No :x: | No :x: |
| Hearts of Iron IV | Yes :heavy_check_mark: | No :x: | No :x: |
8 changes: 8 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

import "errors"

var (
ErrNoMatch = errors.New("cannot detect bytes pattern to patch. Most likely patcher are outdated due to game updates")
ErrCantLocate = errors.New("cannot locate file in current directory")
)
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/goshathebusiness/eu4-checksum-patcher
module github.com/goshathebusiness/universal-checksum-patcher

go 1.19

Expand Down
16 changes: 16 additions & 0 deletions hex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

const (
limit = 14
startLength = 3
endLength = 6
)

var (
start1 = []byte{0x48, 0x8B, 0x12}
start2 = []byte{0x48, 0x8D, 0x0D}
start3 = []byte{0x48, 0x8B, 0xD0}

end = []byte{0x85, 0xC0, 0x0F, 0x94, 0xC3, 0xE8}
replacement = []byte{0x31, 0xC0, 0x0F, 0x94, 0xC3, 0xE8}
)
23 changes: 12 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ package main

import (
"fmt"
"runtime"

"github.com/manifoldco/promptui"
)

func main() {

OS := runtime.GOOS
const (
eu4 = "eu4.exe"
hoi4 = "hoi4.exe"
)

promptGame := promptui.Select{
func main() {
prompt := promptui.Select{
Label: "Select game to patch",
Items: []string{
"Europa Universalis IV",
Expand All @@ -20,7 +21,7 @@ func main() {
HideHelp: true,
}

_, result, err := promptGame.Run()
_, result, err := prompt.Run()

if err != nil {
fmt.Printf("Prompt failed %v\n", err)
Expand All @@ -29,18 +30,18 @@ func main() {

switch result {
case "Europa Universalis IV":
err = applyPatch("eu4", OS)
err = applyPatch(eu4)
case "Hearts of Iron IV":
err = applyPatch("hoi4", OS)
err = applyPatch(hoi4)
}

if err != nil {
fmt.Println("ERROR:", err)
fmt.Println("Patch not installed, no file has been changed")
fmt.Println("Wasn't not installed, file hasn't changed")
} else {
fmt.Println("Patch successfully installed, your original executable has been backuped in [original name]_backup")
fmt.Println("Patch successfully installed, your original executable has been backuped in [original name].backup")
}

fmt.Println("Press enter to exit")
fmt.Scanln()
_, _ = fmt.Scanln()
}
176 changes: 44 additions & 132 deletions patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,170 +2,82 @@ package main

import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)

func printBytesArrInHex(arr []byte) {
for _, b := range arr {
fmt.Printf("%X", b)
func applyPatch(filename string) error {
_, err := os.Stat(filename)
if errors.Is(err, os.ErrNotExist) {
return ErrCantLocate
}
fmt.Println()
}

func normalizeHex(arr []string) []string {
for i, h := range arr {
arr[i] = strings.TrimLeft(h, "0")
bytes, err := os.ReadFile(filename)
if err != nil {
return err
}

return arr
}

func backupFile(originalFileName, backupFileName string) error {
backupData, err := os.ReadFile(originalFileName)
err = modifyBytes(bytes)
if err != nil {
return err
}
backupOutFile, err := os.Create(backupFileName)

err = backupFile(filename)
if err != nil {
return err
}
backupOutFile.Write(backupData)
backupOutFile.Close()

return nil
}
out, err := os.Create(filename)
if err != nil {
return err
}

func compareExes(exeAName, exeBName string) bool {
byteA, err := os.ReadFile(exeAName)
_, err = out.Write(bytes)
if err != nil {
return false
return err
}
byteB, err := os.ReadFile(exeBName)
err = out.Close()
if err != nil {
return false
return err
}

return reflect.DeepEqual(byteA, byteB)
return nil
}

var (
hexExistsEU4Windows = []string{"48", "8D", "0D", "??", "??", "??", "??", "E8", "??", "??", "??", "??", "85", "C0", "0F", "94", "C3", "E8"}
hexWantedEU4Windows = []string{"48", "8D", "0D", "??", "??", "??", "??", "E8", "??", "??", "??", "??", "31", "C0", "0F", "94", "C3", "E8"}

hexExistsHOI4Windows = []string{"48", "??", "??", "??", "??", "??", "??", "E8", "??", "??", "??", "??", "85", "C0", "0F", "94", "C3", "E8"}
hexWantedHOI4Windows = []string{"48", "??", "??", "??", "??", "??", "??", "E8", "??", "??", "??", "??", "31", "C0", "0F", "94", "C3", "E8"}
)

func applyPatch(originalFileName, OS string) error {

var hexExists, hexWanted []string
var fileExtension string

if strings.Contains(originalFileName, "eu4") {
hexExists = hexExistsEU4Windows
hexWanted = hexWantedEU4Windows

} else if strings.Contains(originalFileName, "hoi4") {

hexExists = hexExistsHOI4Windows
hexWanted = hexWantedHOI4Windows

} else {
return fmt.Errorf("not supported executable")
}
func isStartCandidate(bytes []byte) bool {
return isByteSlicesEqual(bytes, start1) || isByteSlicesEqual(bytes, start2) || isByteSlicesEqual(bytes, start3)
}

switch OS {
case "windows":
fileExtension = ".exe"
default:
return fmt.Errorf("this OS (%s) is not supported", OS)
func isEndCandidate(bytes []byte) bool {
return isByteSlicesEqual(bytes, end)
}

}
func modifyBytes(bytes []byte) error {
atLeastOnePatched := false

byteExists := make([]byte, len(hexExists))
byteWanted := make([]byte, len(hexWanted))
for i := range hexExists {
var value int64
if hexExists[i] == "??" {
value = 0
} else {
value, _ = strconv.ParseInt(hexExists[i], 16, 16)
for i := 0; i < len(bytes); i++ {
if i > len(bytes)-limit {
break
}
byteExists[i] = byte(value)
}
for i := range hexWanted {
var value int64
if hexWanted[i] == "??" {
value = 0
} else {
value, _ = strconv.ParseInt(hexWanted[i], 16, 16)
}
byteWanted[i] = byte(value)
}

if _, err := os.Stat(originalFileName + fileExtension); errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("cannot locate %s in current folder", originalFileName+fileExtension)
}

go func() {
backupFile(originalFileName+fileExtension, originalFileName+"_backup"+fileExtension)
}()

originalByte, err := os.ReadFile(originalFileName + fileExtension)
if err != nil {
return err
}
if !isStartCandidate(bytes[i : i+startLength]) {
continue
}

finalByte := originalByte

matchesNeeded := len(byteExists)
matches := 0
status := false

for i := 0; i < len(finalByte); i++ {
if finalByte[i] == byteExists[0] {
matches++
for j := range byteExists {
if (finalByte[i+j] == byteExists[j]) || (byteExists[j] == 0) {
matches++
} else {
matches = 0
break
}
for j := i + startLength; j < i+startLength+limit && j < len(bytes)-endLength; j++ {
if !isEndCandidate(bytes[j : j+endLength]) {
continue
}
if matches >= matchesNeeded {
for k := range byteExists {
if byteExists[k] != 0 {
finalByte[i+k] = byteWanted[k]
}
}
status = true
}
}
}

if !status {
os.Remove(originalFileName + "_backup" + fileExtension)
return fmt.Errorf("unsupported version of %s or it's patched already. Patch has not been applied", originalFileName+fileExtension)
}
for k := 0; k < len(replacement); k++ {
bytes[j+k] = replacement[k]
}

out, err := os.Create(originalFileName + fileExtension)
if err != nil {
return err
atLeastOnePatched = true
}
}

_, err = out.Write(finalByte)
if err != nil {
return err
if atLeastOnePatched {
return nil
}
err = out.Close()
if err != nil {
return err
}

return nil
return ErrNoMatch
}
36 changes: 0 additions & 36 deletions test_eu4_patch_test.go

This file was deleted.

Loading