diff --git a/FyneApp.toml b/FyneApp.toml new file mode 100644 index 0000000..250684e --- /dev/null +++ b/FyneApp.toml @@ -0,0 +1,8 @@ +Website = "https://danpetrov.xyz/tasukeru" + +[Details] + Icon = "Tasukeru.png" + Name = "Tasukeru" + ID = "com.tasukeru.app" + Version = "1.1.0" + Build = 17 diff --git a/Makefile b/Makefile index c6b6538..b626290 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,33 @@ -BINARY_NAME=tasukeru +BINARY_NAME := tasukeru -all: build +.PHONY: all build compile-cli compile-windows compile-mac clean run format build: - go build -o bin/${BINARY_NAME} . + @echo "Building tasukeru build for the current platform" + go build -o bin/${BINARY_NAME} -ldflags '-s -w' . -compile: - @echo "Compiling for every OS and Platform" +compile-cli: + @echo "Compiling simple CLI for every OS and Platform" GOOS=darwin GOARCH=amd64 go build -o bin/${BINARY_NAME}-darwin-amd64 . - GOOS=darwin GOARCH=arm64 go build -o bin/${BINARY_NAME}-darwin-arm64 . - GOOS=windows GOARCH=amd64 go build -o bin/${BINARY_NAME}-windows-amd64.exe . - GOOS=windows GOARCH=arm64 go build -o bin/${BINARY_NAME}-windows-arm64.exe . - GOOS=linux GOARCH=amd64 go build -o bin/${BINARY_NAME}-linux-amd64 . - GOOS=linux GOARCH=arm64 go build -o bin/${BINARY_NAME}-linux-arm64 . + fyne-cross windows -ldflags '-s -w' -arch amd64 -console -name tasukeru-cli.exe + +compile-windows: FyneApp.toml + @echo "Building native Windows cross compiled build" + fyne-cross windows -ldflags '-s -w' + +compile-mac: FyneApp.toml + @echo "Building native Mac app" + fyne-cross darwin -ldflags '-s -w' + +all: compile-cli compile-windows compile-mac clean: go clean rm ./bin/* -run: - go run main.go +run: build + @echo "Running dev build of Tasukeru" + ./bin/${BINARY_NAME} format: @echo "Formatting the entire project" diff --git a/README.md b/README.md index 91f5945..7bd1c44 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # HoloCure Save File Transfer Tool +![Tasukeru GUI demo](https://i.imgur.com/HDohzzB.png) + This is a small tool to import [HoloCure](https://kay-yu.itch.io/holocure) save files from one PC to another. ## Download @@ -9,13 +11,34 @@ Pick the executable matching your architecture (note: HoloCure currently only ru ## Usage -1. Build a release by running `make` or download a release for your platform from [Releases](https://github.com/DaniruKun/tasukeru/releases) -2. Get the save file from the source PC at `Users\[your username]\AppData\Local\HoloCure\save.dat` and move it to the target PC -3. Play HoloCure **at least once** one the target PC -4. On the target PC, drag and drop the `save.dat` onto `tasukeru-*.exe` -5. When prompted, press `Enter` +1. Get the save file from the source PC at `Users\[your username]\AppData\Local\HoloCure\save.dat` and move it to the target PC +2. Play HoloCure **at least once** one the target PC +3. Launch `Tasukeru.exe` +4. Open the save file you want to import +5. Press `Import` 6. The save should now be imported +## Build + +There are 2 build options: as a GUI app, and as a CLI: + +### GUI + +Install [fyne-cross](https://github.com/fyne-io/fyne-cross). +Then run + +```shell +make compile-windows +``` + +### CLI + +```shell +make compile-cli +``` + +This will produce binaries for each platform in the `bin` directory. + ## Advanced You can manually call the executable and pass arguments directly. @@ -33,5 +56,5 @@ E.g. `tasukeru-windows-amd64.exe saveA.dat save.dat` will produce the patched `s On Unix systems you can quickly inspect a save file with ```sh -base64 --decode -i save.dat` +base64 --decode -i save.dat ``` diff --git a/Tasukeru.png b/Tasukeru.png new file mode 100644 index 0000000..63414aa Binary files /dev/null and b/Tasukeru.png differ diff --git a/go.mod b/go.mod index 57a40de..1424a07 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,28 @@ module github.com/DaniruKun/tasukeru go 1.18 -require github.com/Songmu/prompter v0.5.1 +require ( + fyne.io/fyne v1.4.3 + github.com/Songmu/prompter v0.5.1 +) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fyne-io/mobile v0.1.2 // indirect + github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 // indirect + github.com/godbus/dbus/v5 v5.0.3 // indirect + github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect + github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect + github.com/stretchr/testify v1.7.0 // indirect + golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect + golang.org/x/text v0.3.2 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 7fec01a..f18a5c3 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,87 @@ +fyne.io/fyne v1.4.3 h1:356CnXCiYrrfaLGsB7qLK3c6ktzyh8WR05v/2RBu51I= +fyne.io/fyne v1.4.3/go.mod h1:8kiPBNSDmuplxs9WnKCkaWYqbcXFy0DeAzwa6PBO9Z8= +github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= github.com/Songmu/prompter v0.5.1 h1:IAsttKsOZWSDw7bV1mtGn9TAmLFAjXbp9I/eYmUUogo= github.com/Songmu/prompter v0.5.1/go.mod h1:CS3jEPD6h9IaLaG6afrl1orTgII9+uDWuw95dr6xHSw= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fyne-io/mobile v0.1.2 h1:0HaXDtOOwyOTn3Umi0uKVCOgJtfX73c6unC4U8i5VZU= +github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 h1:q521PfSp5/z6/sD9FZZOWj4d1MLmfQW8PkRnI9M6PCE= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= +github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= +github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gui.go b/gui.go new file mode 100644 index 0000000..187d7b4 --- /dev/null +++ b/gui.go @@ -0,0 +1,114 @@ +package main + +import ( + "io" + "net/url" + + "fyne.io/fyne" + "fyne.io/fyne/app" + "fyne.io/fyne/container" + "fyne.io/fyne/dialog" + "fyne.io/fyne/storage" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + "github.com/DaniruKun/tasukeru/holocure" +) + +// Starts a blocking event loop of the GUI. +func RunGUI() { + a := app.New() + w := a.NewWindow("Tasukeru") + w.Resize(fyne.NewSize(800, 600)) + + var srcDec []byte + + targetSaveFilePath := holocure.SaveFilePath() + + confirmButton := widget.NewButtonWithIcon("Import", theme.ConfirmIcon(), func() { + targetDec := mergeFiles(srcDec, targetSaveFilePath) + err := holocure.WriteSaveFile(targetSaveFilePath, targetDec) + if err != nil { + dialog.ShowError(err, w) + return + } + dialog.NewInformation("Result", "Save imported successfully!", w).Show() + }) + confirmButton.Hide() + + openFileButtonLabel := widget.NewLabel("Select save file to import") + openFileButtonLabel.Alignment = fyne.TextAlignCenter + openFileButton := widget.NewButton("Open file", func() { + fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) { + if err != nil { + dialog.ShowError(err, w) + return + } + if reader == nil { + return + } + + srcDec, err = holocure.Decode(loadFile(reader)) + if err != nil { + dialog.ShowError(err, w) + return + } + + confirmButton.Show() + }, w) + + fd.SetFilter(storage.NewExtensionFileFilter([]string{".dat"})) + fd.Resize(fyne.NewSize(1000, 800)) + fd.Show() + }) + openFileButton.Icon = theme.FileIcon() + + aboutUrl, err := url.Parse(HomePage) + check(err) + aboutHyperLink := widget.NewHyperlink("About", aboutUrl) + aboutHyperLink.Alignment = fyne.TextAlignTrailing + versionLabel := widget.NewLabelWithStyle("Version "+Version, fyne.TextAlignTrailing, fyne.TextStyle{Monospace: true}) + + box := container.NewVBox( + openFileButtonLabel, + openFileButton, + confirmButton, + aboutHyperLink, + versionLabel, + ) + + w.SetContent(box) + w.ShowAndRun() +} + +func loadFile(f fyne.URIReadCloser) []byte { + data, err := io.ReadAll(f) + if err != nil { + fyne.LogError("Failed to load file data", err) + return nil + } + return data +} + +func mergeFiles(srcDec []byte, targetSaveFilePath string) []byte { + var start, end int + + start, end = holocure.FindSaveBlockStartEnd(&srcDec) + srcSaveBlock := srcDec[start : end+1] + + targetDec, err := holocure.DecodeSaveFile(targetSaveFilePath) + check(err) + + start, _ = holocure.FindSaveBlockStartEnd(&targetDec) + + for i, char := range srcSaveBlock { + targetOffset := start + i + + if targetOffset >= len(targetDec) { + targetDec = append(targetDec, char) + } else { + targetDec[targetOffset] = char + } + } + + return targetDec +} diff --git a/holocure/holocure.go b/holocure/holocure.go new file mode 100644 index 0000000..8f225ee --- /dev/null +++ b/holocure/holocure.go @@ -0,0 +1,82 @@ +package holocure + +import ( + b64 "encoding/base64" + "fmt" + "os" + "path/filepath" +) + +const defaultSaveFileName = "save.dat" + +// Returns the default HoloCure save file location for the current platform. +func SaveFilePath() string { + dir, err := os.UserCacheDir() + check(err) + return filepath.Join(dir, "HoloCure", defaultSaveFileName) +} + +// It seems that the start offset will always be the same +// across machines, but safer to find save block dynamically +func FindSaveBlockStartEnd(data *[]byte) (start, end int) { + for offset, char := range *data { + if char == 0x7B && (*data)[offset+1] == 0x20 { + start = offset + } + if char == 0x7D && (*data)[offset-1] == 0x20 { + end = offset + } + } + return +} + +func Decode(data []byte) ([]byte, error) { + return b64.URLEncoding.DecodeString(string(data)) +} + +func DecodeSaveFile(filePath string) ([]byte, error) { + srcDat, err := os.ReadFile(filePath) + check(err) + srcDec, err := Decode(srcDat) + return srcDec, err +} + +func WriteSaveFile(filePath string, data []byte) error { + targetEnc := b64.URLEncoding.EncodeToString(data) + return os.WriteFile(filePath, []byte(targetEnc), 0644) +} + +// Merges the source save file path into the target one. +// Returns the raw bytes of the patches save. +func MergeSaves(sourceSaveFilePath, targetSaveFilePath string) []byte { + fmt.Println("reading origin save file", sourceSaveFilePath) + srcDec, err := DecodeSaveFile(sourceSaveFilePath) + check(err) + var start, end int + + start, end = FindSaveBlockStartEnd(&srcDec) + srcSaveBlock := srcDec[start : end+1] + + targetDec, err := DecodeSaveFile(targetSaveFilePath) + check(err) + + start, _ = FindSaveBlockStartEnd(&targetDec) + + // iterate over the source save block and overwrite the dst save block with its data + for i, char := range srcSaveBlock { + targetOffset := start + i + + if targetOffset >= len(targetDec) { + targetDec = append(targetDec, char) + } else { + targetDec[targetOffset] = char + } + } + return targetDec +} + +func check(e error) { + if e != nil { + panic(e) + } +} diff --git a/main.go b/main.go index f06d4a2..0dcae0c 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,15 @@ package main import ( - b64 "encoding/base64" "fmt" "os" - "path/filepath" + "github.com/DaniruKun/tasukeru/holocure" "github.com/Songmu/prompter" ) -const defaultSaveFileName = "save.dat" -const version = "1.0" +const Version = "1.1.0" +const HomePage = "https://danpetrov.xyz/tasukeru" func check(e error) { if e != nil { @@ -37,96 +36,44 @@ This program comes with ABSOLUTELY NO WARRANTY; for details see the Github link. This is free software, and you are welcome to redistribute it under certain conditions. -Website: https://danpetrov.xyz/tasukeru +Website: %s I am not affiliated with Cover Corp. or Kay Yu in any way. ` - fmt.Printf(header, version) -} - -func holoCureSaveFilePath() string { - dir, err := os.UserCacheDir() - check(err) - return filepath.Join(dir, "HoloCure", defaultSaveFileName) -} - -// generally it seems that the start offset will always be the same -// across machines, but safer to find save block dynamically -func getSaveBlockStartEnd(srcDec *[]byte) (start, end int) { - for offset, char := range *srcDec { - if char == 0x7B && (*srcDec)[offset+1] == 0x20 { - start = offset - } - if char == 0x7D && (*srcDec)[offset+1] == 0x00 { - end = offset - } - } - return + fmt.Printf(header, Version, HomePage) } func main() { - printHeader() args := os.Args if len(args) < 2 { - // normally when used as drag n drop on windows, will be exactly 2 - fmt.Println("not enough arguments provided") - fmt.Println("did you Drag n Drop the source save file onto tasukeru.exe ?") - fmt.Println("do not forget to Drag n Drop the new save.dat onto tasukeru.exe") - - waitQuit() - os.Exit(1) + // Run the UI + RunGUI() + } else { + CLI(args) } +} - var start, end int - - sourceSaveFilePath := args[1] - - fmt.Println("reading origin save file", sourceSaveFilePath) - - srcDat, err := os.ReadFile(sourceSaveFilePath) - check(err) - srcDec, err := b64.URLEncoding.DecodeString(string(srcDat)) - check(err) - - start, end = getSaveBlockStartEnd(&srcDec) - - srcSaveBlock := srcDec[start : end+1] +func CLI(args []string) { + printHeader() + var sourceSaveFilePath, targetSaveFilePath string - var targetFilePath string + sourceSaveFilePath = args[1] if len(args) == 3 { - targetFilePath = args[2] + targetSaveFilePath = args[2] } else { - targetFilePath = holoCureSaveFilePath() + targetSaveFilePath = holocure.SaveFilePath() } - targetDat, err := os.ReadFile(targetFilePath) - check(err) - targetDec, err := b64.URLEncoding.DecodeString(string(targetDat)) - check(err) - - start, _ = getSaveBlockStartEnd(&targetDec) - - // iterate over the source save block and overwrite the dst save block with its data - for i, char := range srcSaveBlock { - targetOffset := start + i - - if targetOffset >= len(targetDec) { - targetDec = append(targetDec, char) - } else { - targetDec[targetOffset] = char - } - } + targetDec := holocure.MergeSaves(sourceSaveFilePath, targetSaveFilePath) - // fmt.Println("patched save:", string(targetDec)) fmt.Println() var confirmed bool = prompter.YN("import new save file? インポートOK?", true) if confirmed { - targetEnc := b64.URLEncoding.EncodeToString(targetDec) - err = os.WriteFile(targetFilePath, []byte(targetEnc), 0644) + err := holocure.WriteSaveFile(targetSaveFilePath, targetDec) check(err) fmt.Println("save file imported succesfully!") waitQuit()