From b1a10870c1b7028e9c57d29bafb937733c5ce56d Mon Sep 17 00:00:00 2001 From: David Vogel Date: Thu, 8 Feb 2024 00:50:11 +0100 Subject: [PATCH] Several changes - Add compatibility for newest Noita beta - Modify STREAMING_CHUNK_TARGET, GRID_MAX_UPDATES_PER_FRAME and GRID_MIN_UPDATES_PER_FRAME magic numbers for a more robust capturing process - Add LimitGroup to util.go - Add webp-level command line flag to define the webp compression level - Rework progress bar to make it work in DZI export mode - Refactor image exporter functions - Use LimitGroup to make DZI export multithreaded - Add BlendMethodFast which doesn't mix tile pixels - Up Go version to 1.22 - Use Dadido3/go-libwebp for WebP encoding --- .github/workflows/build-release.yml | 2 +- .github/workflows/build-test.yml | 2 +- .vscode/settings.json | 3 ++ bin/stitch/README.md | 4 +- bin/stitch/blend-methods.go | 19 +++++++- bin/stitch/dzi.go | 63 +++++++++++++++++++++++--- bin/stitch/export-dzi.go | 8 ++-- bin/stitch/export-jpeg.go | 39 ++++++++++++++--- bin/stitch/export-png.go | 39 ++++++++++++++--- bin/stitch/export-webp.go | 45 ++++++++++++++++--- bin/stitch/main.go | 68 +++++++++++++++-------------- bin/stitch/util.go | 44 ++++++++++++++++++- files/modification.lua | 20 +++++++++ go.mod | 4 +- go.sum | 8 +++- 15 files changed, 300 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 14745b3..62c297f 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.21 + go-version: ^1.22 - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4a06484..c02c423 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: ^1.21 + go-version: ^1.22 - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.vscode/settings.json b/.vscode/settings.json index d7f067c..3cc67bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "basicfont", "bytecode", "cheggaaa", + "Dadido", "dofile", "dont", "Downscales", @@ -26,6 +27,7 @@ "Lanczos", "lann", "ldflags", + "libwebp", "linearize", "longleg", "lowram", @@ -57,6 +59,7 @@ "Vogel", "Voronoi", "webp", + "wepb", "xmax", "xmin", "ymax", diff --git a/bin/stitch/README.md b/bin/stitch/README.md index 529f924..ffc4413 100644 --- a/bin/stitch/README.md +++ b/bin/stitch/README.md @@ -36,7 +36,7 @@ example list of files: If set to 1, only the newest tile will be used for any resulting pixel. Use 1 to prevent ghosting and blurry objects. - `input string` - The source path of the image tiles to be stitched. Defaults to "./..//..//output") + The source path of the image tiles to be stitched. Defaults to "./..//..//output" - `entities string` The path to the `entities.json` file. This contains Noita specific entity data. Defaults to "./../../output/entities.json". - `player-path string` @@ -48,6 +48,8 @@ example list of files: The size of the resulting deep zoom image (DZI) tiles in pixels. Defaults to 512. - `dzi-tile-overlap` The number of additional pixels around every deep zoom image (DZI) tile. Defaults to 2. + - `wepb-level` + Compression level of WebP files, from 0 (fast) to 9 (slow, best compression). Defaults to 8. - `xmax int` Right bound of the output rectangle. This coordinate is not included in the output. - `xmin int` diff --git a/bin/stitch/blend-methods.go b/bin/stitch/blend-methods.go index 812dcbb..9731e31 100644 --- a/bin/stitch/blend-methods.go +++ b/bin/stitch/blend-methods.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 David Vogel +// Copyright (c) 2022-2024 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -8,6 +8,7 @@ package main import ( "image" "image/color" + "image/draw" "math" "sort" ) @@ -106,7 +107,7 @@ func (b BlendMethodVoronoi) Draw(tiles []*ImageTile, destImage *image.RGBA) { images = append(images, tile.GetImage()) } - // Create arrays to be reused every pixel. + // Create color variables reused every pixel. var col color.RGBA var centerDistSqrMin int @@ -147,3 +148,17 @@ func (b BlendMethodVoronoi) Draw(tiles []*ImageTile, destImage *image.RGBA) { } } } + +// BlendMethodFast just draws all tiles into the destination image. +// No mixing is done, and this is very fast when there is no or minimal tile overlap. +type BlendMethodFast struct{} + +// Draw implements the StitchedImageBlendMethod interface. +func (b BlendMethodFast) Draw(tiles []*ImageTile, destImage *image.RGBA) { + for _, tile := range tiles { + if image := tile.GetImage(); image != nil { + bounds := image.Bounds() + draw.Draw(destImage, bounds, image, bounds.Min, draw.Src) + } + } +} diff --git a/bin/stitch/dzi.go b/bin/stitch/dzi.go index 320fd77..d2e4e91 100644 --- a/bin/stitch/dzi.go +++ b/bin/stitch/dzi.go @@ -12,9 +12,13 @@ import ( "log" "os" "path/filepath" + "runtime" "strconv" "sync" + "sync/atomic" "time" + + "github.com/cheggaaa/pb/v3" ) type DZI struct { @@ -99,9 +103,49 @@ func (d DZI) ExportDZIDescriptor(outputPath string) error { } // ExportDZITiles exports the single image tiles for every zoom level. -func (d DZI) ExportDZITiles(outputDir string) error { +func (d DZI) ExportDZITiles(outputDir string, bar *pb.ProgressBar, webPLevel int) error { log.Printf("Creating DZI tiles in %q.", outputDir) + const scaleDivider = 2 + + var exportedTiles atomic.Int64 + + // If there is a progress bar, start a goroutine that regularly updates it. + // We will base that on the number of exported tiles. + if bar != nil { + + // Count final number of tiles. + bounds := d.stitchedImage.bounds + var finalTiles int64 + for zoomLevel := d.maxZoomLevel; zoomLevel >= 0; zoomLevel-- { + for iY := 0; iY <= (bounds.Dy()-1)/d.tileSize; iY++ { + for iX := 0; iX <= (bounds.Dx()-1)/d.tileSize; iX++ { + finalTiles++ + } + } + bounds = image.Rect(DivideFloor(bounds.Min.X, scaleDivider), DivideFloor(bounds.Min.Y, scaleDivider), DivideCeil(bounds.Max.X, scaleDivider), DivideCeil(bounds.Max.Y, scaleDivider)) + } + bar.SetRefreshRate(250 * time.Millisecond).SetTotal(finalTiles).Start() + + done := make(chan struct{}) + defer func() { + done <- struct{}{} + bar.SetCurrent(bar.Total()).Finish() + }() + + go func() { + ticker := time.NewTicker(250 * time.Millisecond) + for { + select { + case <-done: + return + case <-ticker.C: + bar.SetCurrent(exportedTiles.Load()) + } + } + }() + } + // Start with the highest zoom level (Where every world pixel is exactly mapped into one image pixel). // Generate all tiles for this level, and then stitch another image (scaled down by a factor of 2) based on the previously generated tiles. // Repeat this process until we have generated level 0. @@ -120,6 +164,7 @@ func (d DZI) ExportDZITiles(outputDir string) error { imageTiles := ImageTiles{} // Export tiles. + lg := NewLimitGroup(runtime.NumCPU()) for iY := 0; iY <= (stitchedImage.bounds.Dy()-1)/d.tileSize; iY++ { for iX := 0; iX <= (stitchedImage.bounds.Dx()-1)/d.tileSize; iX++ { rect := image.Rect(iX*d.tileSize, iY*d.tileSize, iX*d.tileSize+d.tileSize, iY*d.tileSize+d.tileSize) @@ -127,11 +172,16 @@ func (d DZI) ExportDZITiles(outputDir string) error { rect = rect.Inset(-d.overlap) img := stitchedImage.SubStitchedImage(rect) filePath := filepath.Join(levelBasePath, fmt.Sprintf("%d_%d%s", iX, iY, d.fileExtension)) - if err := exportWebPSilent(img, filePath); err != nil { - return fmt.Errorf("failed to export WebP: %w", err) - } - scaleDivider := 2 + lg.Add(1) + go func() { + defer lg.Done() + if err := exportWebP(img, filePath, webPLevel); err != nil { + log.Printf("Failed to export WebP: %v", err) + } + exportedTiles.Add(1) + }() + imageTiles = append(imageTiles, ImageTile{ fileName: filePath, modTime: time.Now(), @@ -143,11 +193,12 @@ func (d DZI) ExportDZITiles(outputDir string) error { }) } } + lg.Wait() // Create new stitched image from the previously exported tiles. // The tiles are already created in a way, that they are scaled down by a factor of 2. var err error - stitchedImage, err = NewStitchedImage(imageTiles, imageTiles.Bounds(), BlendMethodMedian{BlendTileLimit: 0}, 128, nil) + stitchedImage, err = NewStitchedImage(imageTiles, imageTiles.Bounds(), BlendMethodFast{}, 128, nil) if err != nil { return fmt.Errorf("failed to run NewStitchedImage(): %w", err) } diff --git a/bin/stitch/export-dzi.go b/bin/stitch/export-dzi.go index 8791c0a..e76e678 100644 --- a/bin/stitch/export-dzi.go +++ b/bin/stitch/export-dzi.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 David Vogel +// Copyright (c) 2023-2024 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -10,9 +10,11 @@ import ( "os" "path/filepath" "strings" + + "github.com/cheggaaa/pb/v3" ) -func exportDZI(stitchedImage *StitchedImage, outputPath string, dziTileSize, dziOverlap int) error { +func exportDZIStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar, dziTileSize, dziOverlap int, webPLevel int) error { descriptorPath := outputPath extension := filepath.Ext(outputPath) outputTilesPath := strings.TrimSuffix(outputPath, extension) + "_files" @@ -30,7 +32,7 @@ func exportDZI(stitchedImage *StitchedImage, outputPath string, dziTileSize, dzi } // Export DZI tiles. - if err := dzi.ExportDZITiles(outputTilesPath); err != nil { + if err := dzi.ExportDZITiles(outputTilesPath, bar, webPLevel); err != nil { return fmt.Errorf("failed to export DZI tiles: %w", err) } diff --git a/bin/stitch/export-jpeg.go b/bin/stitch/export-jpeg.go index 15038c2..13e0acd 100644 --- a/bin/stitch/export-jpeg.go +++ b/bin/stitch/export-jpeg.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 David Vogel +// Copyright (c) 2023-2024 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -11,15 +11,44 @@ import ( "image/jpeg" "log" "os" + "time" + + "github.com/cheggaaa/pb/v3" ) -func exportJPEG(stitchedImage image.Image, outputPath string) error { +func exportJPEGStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar) error { log.Printf("Creating output file %q.", outputPath) - return exportJPEGSilent(stitchedImage, outputPath) + // If there is a progress bar, start a goroutine that regularly updates it. + // We will base the progress on the number of pixels read from the stitched image. + if bar != nil { + _, max := stitchedImage.Progress() + bar.SetRefreshRate(250 * time.Millisecond).SetTotal(int64(max)).Start() + + done := make(chan struct{}) + defer func() { + done <- struct{}{} + bar.SetCurrent(bar.Total()).Finish() + }() + + go func() { + ticker := time.NewTicker(250 * time.Millisecond) + for { + select { + case <-done: + return + case <-ticker.C: + value, max := stitchedImage.Progress() + bar.SetCurrent(int64(value)).SetTotal(int64(max)) + } + } + }() + } + + return exportJPEG(stitchedImage, outputPath) } -func exportJPEGSilent(stitchedImage image.Image, outputPath string) error { +func exportJPEG(img image.Image, outputPath string) error { f, err := os.Create(outputPath) if err != nil { return fmt.Errorf("failed to create file: %w", err) @@ -30,7 +59,7 @@ func exportJPEGSilent(stitchedImage image.Image, outputPath string) error { Quality: 80, } - if err := jpeg.Encode(f, stitchedImage, options); err != nil { + if err := jpeg.Encode(f, img, options); err != nil { return fmt.Errorf("failed to encode image %q: %w", outputPath, err) } diff --git a/bin/stitch/export-png.go b/bin/stitch/export-png.go index b95c6ba..f475c3e 100644 --- a/bin/stitch/export-png.go +++ b/bin/stitch/export-png.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 David Vogel +// Copyright (c) 2023-2024 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -11,15 +11,44 @@ import ( "image/png" "log" "os" + "time" + + "github.com/cheggaaa/pb/v3" ) -func exportPNG(stitchedImage image.Image, outputPath string) error { +func exportPNGStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar) error { log.Printf("Creating output file %q.", outputPath) - return exportPNGSilent(stitchedImage, outputPath) + // If there is a progress bar, start a goroutine that regularly updates it. + // We will base the progress on the number of pixels read from the stitched image. + if bar != nil { + _, max := stitchedImage.Progress() + bar.SetRefreshRate(250 * time.Millisecond).SetTotal(int64(max)).Start() + + done := make(chan struct{}) + defer func() { + done <- struct{}{} + bar.SetCurrent(bar.Total()).Finish() + }() + + go func() { + ticker := time.NewTicker(250 * time.Millisecond) + for { + select { + case <-done: + return + case <-ticker.C: + value, max := stitchedImage.Progress() + bar.SetCurrent(int64(value)).SetTotal(int64(max)) + } + } + }() + } + + return exportPNG(stitchedImage, outputPath) } -func exportPNGSilent(stitchedImage image.Image, outputPath string) error { +func exportPNG(img image.Image, outputPath string) error { f, err := os.Create(outputPath) if err != nil { return fmt.Errorf("failed to create file: %w", err) @@ -30,7 +59,7 @@ func exportPNGSilent(stitchedImage image.Image, outputPath string) error { CompressionLevel: png.DefaultCompression, } - if err := encoder.Encode(f, stitchedImage); err != nil { + if err := encoder.Encode(f, img); err != nil { return fmt.Errorf("failed to encode image %q: %w", outputPath, err) } diff --git a/bin/stitch/export-webp.go b/bin/stitch/export-webp.go index 2fa3b46..3207ad0 100644 --- a/bin/stitch/export-webp.go +++ b/bin/stitch/export-webp.go @@ -10,18 +10,46 @@ import ( "image" "log" "os" + "time" - "github.com/chai2010/webp" + "github.com/Dadido3/go-libwebp/webp" + "github.com/cheggaaa/pb/v3" ) -func exportWebP(stitchedImage image.Image, outputPath string) error { +func exportWebPStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar, webPLevel int) error { log.Printf("Creating output file %q.", outputPath) - return exportWebPSilent(stitchedImage, outputPath) + // If there is a progress bar, start a goroutine that regularly updates it. + // We will base the progress on the number of pixels read from the stitched image. + if bar != nil { + _, max := stitchedImage.Progress() + bar.SetRefreshRate(250 * time.Millisecond).SetTotal(int64(max)).Start() + + done := make(chan struct{}) + defer func() { + done <- struct{}{} + bar.SetCurrent(bar.Total()).Finish() + }() + + go func() { + ticker := time.NewTicker(250 * time.Millisecond) + for { + select { + case <-done: + return + case <-ticker.C: + value, max := stitchedImage.Progress() + bar.SetCurrent(int64(value)).SetTotal(int64(max)) + } + } + }() + } + + return exportWebP(stitchedImage, outputPath, webPLevel) } -func exportWebPSilent(stitchedImage image.Image, outputPath string) error { - bounds := stitchedImage.Bounds() +func exportWebP(img image.Image, outputPath string, webPLevel int) error { + bounds := img.Bounds() if bounds.Dx() > 16383 || bounds.Dy() > 16383 { return fmt.Errorf("image size exceeds the maximum allowed size (16383) of a WebP image: %d x %d", bounds.Dx(), bounds.Dy()) } @@ -32,7 +60,12 @@ func exportWebPSilent(stitchedImage image.Image, outputPath string) error { } defer f.Close() - if err = webp.Encode(f, stitchedImage, &webp.Options{Lossless: true}); err != nil { + webPConfig, err := webp.ConfigLosslessPreset(webPLevel) + if err != nil { + return fmt.Errorf("failed to create webP config: %v", err) + } + + if err = webp.Encode(f, img, webPConfig); err != nil { return fmt.Errorf("failed to encode image %q: %w", outputPath, err) } diff --git a/bin/stitch/main.go b/bin/stitch/main.go index ddee631..6580482 100644 --- a/bin/stitch/main.go +++ b/bin/stitch/main.go @@ -12,7 +12,6 @@ import ( "log" "path/filepath" "strings" - "sync" "time" "github.com/1lann/promptui" @@ -27,6 +26,7 @@ var flagScaleDivider = flag.Int("divide", 1, "A downscaling factor. 2 will produ var flagBlendTileLimit = flag.Int("blend-tile-limit", 9, "Limits median blending to the n newest tiles by file modification time. If set to 0, all available tiles will be median blended.") var flagDZITileSize = flag.Int("dzi-tile-size", 512, "The size of the resulting deep zoom image (DZI) tiles in pixels.") var flagDZIOverlap = flag.Int("dzi-tile-overlap", 2, "The number of additional pixels around every deep zoom image (DZI) tile.") +var flagWebPLevel = flag.Int("wepb-level", 8, "Compression level of WebP files, from 0 (fast) to 9 (slow, best compression).") var flagXMin = flag.Int("xmin", 0, "Left bound of the output rectangle. This coordinate is included in the output.") var flagYMin = flag.Int("ymin", 0, "Upper bound of the output rectangle. This coordinate is included in the output.") var flagXMax = flag.Int("xmax", 0, "Right bound of the output rectangle. This coordinate is not included in the output.") @@ -287,11 +287,35 @@ func main() { fmt.Sscanf(result, "%d", flagDZIOverlap) } - startTime := time.Now() + // Query the user, if there were no cmd arguments given. + if flag.NFlag() == 0 && (fileExtension == ".dzi" || fileExtension == ".webp") { + prompt := promptui.Prompt{ + Label: "Enter WebP compression level:", + Default: fmt.Sprint(*flagWebPLevel), + AllowEdit: true, + Validate: func(s string) error { + var num int + _, err := fmt.Sscanf(s, "%d", &num) + if err != nil { + return err + } + if int(num) < 0 { + return fmt.Errorf("level must be at least 0") + } + if int(num) > 9 { + return fmt.Errorf("level must not be larger than 9") + } - bar := pb.Full.New(0) - var wg sync.WaitGroup - done := make(chan struct{}) + return nil + }, + } + + result, err := prompt.Run() + if err != nil { + log.Panicf("Error while getting user input: %v.", err) + } + fmt.Sscanf(result, "%d", flagWebPLevel) + } blendMethod := BlendMethodMedian{ BlendTileLimit: *flagBlendTileLimit, // Limit median blending to the n newest tiles by file modification time. @@ -301,53 +325,31 @@ func main() { if err != nil { log.Panicf("NewStitchedImage() failed: %v.", err) } - _, max := stitchedImage.Progress() - bar.SetTotal(int64(max)).Start().SetRefreshRate(250 * time.Millisecond) - // Query progress and draw progress bar. - wg.Add(1) - go func() { - defer wg.Done() - - ticker := time.NewTicker(250 * time.Millisecond) - for { - select { - case <-done: - value, _ := stitchedImage.Progress() - bar.SetCurrent(int64(value)) - bar.Finish() - return - case <-ticker.C: - value, _ := stitchedImage.Progress() - bar.SetCurrent(int64(value)) - } - } - }() + bar := pb.Full.New(0) switch fileExtension { case ".png": - if err := exportPNG(stitchedImage, *flagOutputPath); err != nil { + if err := exportPNGStitchedImage(stitchedImage, *flagOutputPath, bar); err != nil { log.Panicf("Export of PNG file failed: %v", err) } case ".jpg", ".jpeg": - if err := exportJPEG(stitchedImage, *flagOutputPath); err != nil { + if err := exportJPEGStitchedImage(stitchedImage, *flagOutputPath, bar); err != nil { log.Panicf("Export of JPEG file failed: %v", err) } case ".webp": - if err := exportWebP(stitchedImage, *flagOutputPath); err != nil { + if err := exportWebPStitchedImage(stitchedImage, *flagOutputPath, bar, *flagWebPLevel); err != nil { log.Panicf("Export of WebP file failed: %v", err) } case ".dzi": - if err := exportDZI(stitchedImage, *flagOutputPath, *flagDZITileSize, *flagDZIOverlap); err != nil { + if err := exportDZIStitchedImage(stitchedImage, *flagOutputPath, bar, *flagDZITileSize, *flagDZIOverlap, *flagWebPLevel); err != nil { log.Panicf("Export of DZI file failed: %v", err) } default: log.Panicf("Unknown output format %q.", fileExtension) } - done <- struct{}{} - wg.Wait() - log.Printf("Created output in %v.", time.Since(startTime)) + log.Printf("Created output in %v.", time.Since(bar.StartTime())) //fmt.Println("Press the enter key to terminate the console screen!") //fmt.Scanln() diff --git a/bin/stitch/util.go b/bin/stitch/util.go index eb7fb47..54b36b5 100644 --- a/bin/stitch/util.go +++ b/bin/stitch/util.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 David Vogel +// Copyright (c) 2019-2024 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -9,6 +9,7 @@ import ( "fmt" "image" "os" + "sync" ) // QuickSelect returns the kth smallest element of the given unsorted list. @@ -96,3 +97,44 @@ func DivideCeil(a, b int) int { return temp } + +// https://gist.github.com/cstockton/d611ced26bb6b4d3f7d4237abb8613c4 +type LimitGroup struct { + wg sync.WaitGroup + mu *sync.Mutex + c *sync.Cond + l, n int +} + +func NewLimitGroup(n int) *LimitGroup { + mu := new(sync.Mutex) + return &LimitGroup{ + mu: mu, + c: sync.NewCond(mu), + l: n, + n: n, + } +} + +func (lg *LimitGroup) Add(delta int) { + lg.mu.Lock() + defer lg.mu.Unlock() + if delta > lg.l { + panic(`LimitGroup: delta must not exceed limit`) + } + for lg.n < 1 { + lg.c.Wait() + } + lg.n -= delta + lg.wg.Add(delta) +} + +func (lg *LimitGroup) Done() { + lg.mu.Lock() + defer lg.mu.Unlock() + lg.n++ + lg.c.Signal() + lg.wg.Done() +} + +func (lg *LimitGroup) Wait() { lg.wg.Wait() } diff --git a/files/modification.lua b/files/modification.lua index 9d881a2..341f2e2 100644 --- a/files/modification.lua +++ b/files/modification.lua @@ -198,6 +198,16 @@ function Modification.SetMemoryOptions(memory) mPlayerNeverDies = function(value) ffi.cast("char*", 0x01305862)[0] = value end, mFreezeAI = function(value) ffi.cast("char*", 0x01305863)[0] = value end, }, + {_Offset = 0x01173F34, _BuildString = "Build Feb 6 2024 15:54:02", -- Steam dev build. + mPostFxDisabled = function(value) ffi.cast("char*", 0x0130982C+0)[0] = value end, + mGuiDisabled = function(value) ffi.cast("char*", 0x0130982C+1)[0] = value end, + mGuiHalfSize = function(value) ffi.cast("char*", 0x0130982C+2)[0] = value end, + mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x0130982C+3)[0] = value end, + mTrailerMode = function(value) ffi.cast("char*", 0x0130982C+4)[0] = value end, + mDayTimeRotationPause = function(value) ffi.cast("char*", 0x0130982C+5)[0] = value end, + mPlayerNeverDies = function(value) ffi.cast("char*", 0x0130982C+6)[0] = value end, + mFreezeAI = function(value) ffi.cast("char*", 0x0130982C+7)[0] = value end, + }, }, }, [false] = { @@ -265,6 +275,13 @@ function Modification.SetMemoryOptions(memory) ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled. end, }, + {_Offset = 0x00FEEFC0, _BuildString = "Build Feb 6 2024 15:58:22", -- Steam build. + enableModDetection = function(value) + local ptr = ffi.cast("char*", 0x006AD611+6) + Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE) + ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled. + end, + }, }, }, } @@ -343,6 +360,9 @@ function Modification.RequiredChanges() magic["GRID_RENDER_BORDER"] = "3" -- This will widen the right side of the virtual rectangle. It also shifts the world coordinates to the right. magic["VIRTUAL_RESOLUTION_OFFSET_X"] = "-3" magic["VIRTUAL_RESOLUTION_OFFSET_Y"] = "0" + magic["STREAMING_CHUNK_TARGET"] = "16" -- Keep more chunks alive. + magic["GRID_MAX_UPDATES_PER_FRAME"] = "1024" -- Allow more pixel physics simulation steps (in 32x32 regions) per frame. With too few, objects can glitch through the terrain/explode. + magic["GRID_MIN_UPDATES_PER_FRAME"] = "0" -- Also allow no updates. else -- Reset some values if there is no custom resolution requested. config["internal_size_w"] = "1280" diff --git a/go.mod b/go.mod index d8c9e9f..0724936 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/Dadido3/noita-mapcap -go 1.21 +go 1.22 require ( github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 - github.com/chai2010/webp v1.1.1 + github.com/Dadido3/go-libwebp v0.3.0 github.com/cheggaaa/pb/v3 v3.1.4 github.com/coreos/go-semver v0.3.1 github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 diff --git a/go.sum b/go.sum index f92e117..1752e42 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,12 @@ github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 h1:LejjvYg4tCW5HO github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1/go.mod h1:cnC/60IoLiDM0GhdKYJ6oO7AwpZe1IQfPnSKlAURgHw= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= +github.com/Dadido3/go-libwebp v0.1.0 h1:aMM8wwOSyq8mk/xL9ze3vQHPygDU8TMXKH0TViwkBLE= +github.com/Dadido3/go-libwebp v0.1.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo= +github.com/Dadido3/go-libwebp v0.2.0 h1:SmssjSkrDkwSOGEpdultvneADGYaEqPbv6FcgausaK0= +github.com/Dadido3/go-libwebp v0.2.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo= +github.com/Dadido3/go-libwebp v0.3.0 h1:Qr3Gt8Kn4qgemezDVnjAJffMB9C0QJhxP+9u0U5mC94= +github.com/Dadido3/go-libwebp v0.3.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= @@ -16,8 +22,6 @@ github.com/benoitkugler/textprocessing v0.0.3 h1:Q2X+Z6vxuW5Bxn1R9RaNt0qcprBfpc2 github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= -github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=