diff --git a/.gitignore b/.gitignore index 723ef36..1338523 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.idea \ No newline at end of file +.idea + +RPG-Maker-ACE-Cheater-Patcher.exe +RPGMakerDecrypter-cli.exe diff --git a/README.md b/README.md index 7bb517a..6f25975 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # RPG Maker VX Ace Cheater -Prototyping... - # How to Patch on Windows - Preparing @@ -11,7 +9,7 @@ Prototyping... ```shell ruby rvdata2-unpacker/rvunpacker.rb decode . ``` - - See [README.md](rvdata2-unpacker/README.md) for more details. + - See [README.md](rvdata2-unpacker/README.md) for more details. - Use [RPGMakerDecrypter-cli.exe](https://github.com/uuksu/RPGMakerDecrypter) to unpack `Game.rgss3a`. - Download `RPGMakerDecrypter-cli.exe` from [https://github.com/uuksu/RPGMakerDecrypter/releases](https://github.com/uuksu/RPGMakerDecrypter/releases). @@ -77,6 +75,13 @@ Prototyping... - `[`: Save current position - `]`: Load saved position +# Dev + +- Download `RPGMakerDecrypter-cli.exe` from https://github.com/uuksu/RPGMakerDecrypter/releases, and put it in root of + project along + with [main.go](main.go). +- Run [build.sh](build.sh) or [build.bat](build.bat) to build the patcher. + # Credits - [RPGMakerDecrypter](https://github.com/uuksu/RPGMakerDecrypter) diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..df6ef85 --- /dev/null +++ b/build.bat @@ -0,0 +1,3 @@ +@echo off + +go build -o RPG-Maker-ACE-Cheater-Patcher.exe diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..4bc33d2 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +export GOOS="windows" +export GOARCH="amd64" + +go build -o RPG-Maker-ACE-Cheater-Patcher.exe diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..efe4efd --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/allape/RPG-Maker-ACE-Cheater + +go 1.23.1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..a2cfe6a --- /dev/null +++ b/main.go @@ -0,0 +1,200 @@ +package main + +import ( + _ "embed" + "fmt" + "log" + "os" + "os/exec" + "path" + "runtime" + "strings" + "time" +) + +const ( + GameRGSS3A = "Game.rgss3a" + ScriptsRVDARA2 = "Scripts.rvdata2" + MainRB = "Main.rb" + SceneBaseRB = "Scene_Base.rb" +) + +const ( + SceneBaseInjectionScript = " AsCheater.update" +) + +//go:embed AsCheater.rb +var CheatScript string + +//go:embed RPGMakerDecrypter-cli.exe +var RPGMakerDecrypterCLIBin []byte + +//go:embed rvdata2-unpacker/rvunpacker.exe +var RVUnpackerBin []byte + +func pause() { + log.Println("Press [Enter] to exit...") + var input string + _, _ = fmt.Scanln(&input) +} + +func Println(args ...any) { + log.Println(args...) +} + +func Fatalln(args ...any) { + log.Println(args...) + pause() + os.Exit(1) +} + +func createExe(name string, data []byte) string { + Println("Creating", name, "...") + file, err := os.CreateTemp(os.TempDir(), name) + if err != nil { + Fatalln("Failed to create", name, ":", err) + } + defer func() { + _ = file.Close() + }() + _, err = file.Write(data) + if err != nil { + Fatalln("Failed to write", name, ":", err) + } + _ = file.Close() + + Println("Created", name, "at", file.Name()) + + return file.Name() +} + +func main() { + if runtime.GOOS != "windows" { + Fatalln("This program only runs on Windows") + } + + // print credits + Println("Credits:") + Println("RPGMakerDecrypter-cli.exe by https://github.com/uuksu/RPGMakerDecrypter") + Println("rvunpacker.exe by https://www.gamedev.net/forums/topic/646333-rpg-maker-vx-ace-data-conversion-utility/") + + Println() + Println("If this program failed to patch your game, please do it manually with the instructions in README.md.") + + Println() + Println("Booting up...") + + time.Sleep(3 * time.Second) + + dec := createExe("RPGMAC-RPGMakerDecrypter-cli.exe", RPGMakerDecrypterCLIBin) + rvu := createExe("RPGMAC-rvunpacker.exe", RVUnpackerBin) + + root := "." + if len(os.Args) > 1 { + root = os.Args[1] + } + + Println("Ready to patch Game.exe at", root) + + stat, err := os.Stat(root) + if err != nil { + Fatalln("Failed to stat", root, ":", err) + } else if !stat.IsDir() { + Fatalln(root, "is not a directory") + } + + scriptsRVDARA2 := path.Join(root, "Data", ScriptsRVDARA2) + _, err = os.Stat(scriptsRVDARA2) + if err != nil { + unzipGame(dec, root) + injectMain(rvu, root) + } else { + Println("Game already unzipped") + injectMain(rvu, root) + } + + Println("Patched Game.exe at", root) + + pause() +} + +func run(name, cwd string, arg ...string) { + cmd := exec.Command(name, arg...) + cmd.Dir = cwd + + output, err := cmd.CombinedOutput() + if len(output) > 0 { + Println(name, ":\n", string(output)) + } + if err != nil { + Fatalln("Failed to run", name, ":", err) + } +} + +func injectMain(exe, root string) { + Println("Patching Game.exe...") + + // decode Scripts.rvdata2 + run(exe, root, "decode", root) + + mainRb := path.Join(root, "Scripts", MainRB) + sceneBaseRb := path.Join(root, "Scripts", SceneBaseRB) + + if _, err := os.Stat(mainRb); err != nil { + Fatalln("Failed to find", mainRb) + } + if _, err := os.Stat(sceneBaseRb); err != nil { + Fatalln("Failed to find", sceneBaseRb) + } + + mainText, err := os.ReadFile(mainRb) + if err != nil { + Fatalln("Failed to read", mainRb, ":", err) + } + + if strings.Contains(string(mainText), CheatScript) { + Println("Game already patched") + return + } + + err = os.WriteFile(mainRb, []byte(fmt.Sprintf("%s\r\n%s", CheatScript, string(mainText))), 0644) + if err != nil { + Fatalln("Failed to write", mainRb, ":", err) + } + + sceneBaseText, err := os.ReadFile(sceneBaseRb) + if err != nil { + Fatalln("Failed to read", sceneBaseRb, ":", err) + } + sceneBaseLines := strings.Split(string(sceneBaseText), "\r\n") + injectPoint := -1 + for i, line := range sceneBaseLines { + if strings.TrimSpace(line) == "def update" { + injectPoint = i + break + } + } + if injectPoint < 0 { + Fatalln("Failed to find 'def update' in", sceneBaseRb) + } + sceneBaseInjectedText := strings.Join(sceneBaseLines[:injectPoint+1], "\r\n") + sceneBaseInjectedText += "\r\n" + SceneBaseInjectionScript + "\r\n" + sceneBaseInjectedText += strings.Join(sceneBaseLines[injectPoint+1:], "\r\n") + err = os.WriteFile(sceneBaseRb, []byte(sceneBaseInjectedText+"\r\n"), 0644) + + // encode Scripts.rvdata2 + run(exe, root, "encode", root) +} + +func unzipGame(exe, root string) { + Println("Unzipping Game.exe...") + src := path.Join(root, GameRGSS3A) + dst := path.Join(root, GameRGSS3A+"~") + run(exe, root, src) + + Println("Renaming", src, "to", dst) + err := os.Rename(src, dst) + if err != nil { + Fatalln("Failed to rename", src, "to", dst, ":", err) + } +} diff --git a/rvdata2-unpacker/.gitignore b/rvdata2-unpacker/.gitignore index 996e13a..4f87566 100644 --- a/rvdata2-unpacker/.gitignore +++ b/rvdata2-unpacker/.gitignore @@ -1,4 +1,3 @@ Data Scripts YAML -RPGMakerDecrypter-cli.exe