Compare commits

...

5 Commits

Author SHA1 Message Date
3cdd56424a Merge pull request 'WIP Doodle++' (#93) from dpp into master
Reviewed-on: #93
2024-04-19 05:49:40 +00:00
33dc17bb19 Doodle++ Code Cleanup 2024-04-18 22:49:12 -07:00
a79601f983 D++ Default Author and Embedded Doodads Error
* Update native.DefaultAuthor to get the name registered from the user's JWT
  license in a way that avoids cyclic dependency errors.
* When plus_dpp.go#GetRegistration succeeds, it updates DefaultAuthor to the
  registered name. The main.go now gets and prints the registered owner to
  ensure this is populated on startup.
* Return correct ErrRegisteredFeature error when the FOSS version fails
  to load embedded doodads.
2024-04-18 22:31:11 -07:00
a06787411d Resolve circular import errors for Doodle++ plugin
* pkg/plus/dpp is the main plugin bridge, and defines nothing but an interface
  that defines the Doodle++ surface area (referring to internal game types such
  as doodad.Doodad or level.Level), but not their implementations.
  * dpp.Driver (an interface) is the main API that other parts of the game will
    call, for example "dpp.Driver.IsLevelSigned()"
  * plus_dpp.go and plus_foss.go provide the dpp.Driver implementation for their
    build; with plus_dpp.go generally forwarding function calls directly to the
    proprietary dpp package and plus_foss.go generally returning false/errors.
  * The bootstrap package simply assigns the above stub function to dpp.Driver
* pkg/plus/bootstrap is a package directly imported by main (in the doodle and
  doodad programs) and it works around circular dependency issues: this package
  simply assigns dpp.Driver to the DPP or FOSS version.

Miscellaneous fixes:

* File->Open in the editor and PlayScene will use the new Open Level window
  instead of loading the legacy GotoLoadMenu scene.
* Deprecated legacy scenes: d.GotoLoadMenu() and d.GotoPlayMenu().
* The doodle-admin program depends on the private dpp package, so can not be
  compiled in FOSS mode.
2024-04-18 22:12:56 -07:00
7eb7f6148c WIP Doodle++ 2024-04-18 20:23:07 -07:00
43 changed files with 501 additions and 623 deletions

View File

@ -1,12 +1,21 @@
# Building Doodle
* [Automated Release Scripts](#automated-release-scripts)
* [Quickstart with bootstrap.py](#quickstart-with-bootstrap-py)
* [Detailed Instructions](#detailed-instructions)
* [Linux](#linux)
* [Flatpak for Linux](#flatpak-for-linux)
* [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
* [Old Docs](#old-docs)
- [Building Doodle](#building-doodle)
- [Dockerfile](#dockerfile)
- [Automated Release Scripts](#automated-release-scripts)
- [Go Environment](#go-environment)
- [Quickstart with bootstrap.py](#quickstart-with-bootstrappy)
- [Detailed Instructions](#detailed-instructions)
- [Fonts](#fonts)
- [Makefile](#makefile)
- [Dependencies](#dependencies)
- [Flatpak for Linux](#flatpak-for-linux)
- [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
- [Windows DLLs](#windows-dlls)
- [Build on macOS from scratch](#build-on-macos-from-scratch)
- [WebAssembly](#webassembly)
- [Build Tags](#build-tags)
- [dpp](#dpp)
# Dockerfile
@ -43,6 +52,21 @@ Other Dockerfiles and scripts used to release the game:
The Docker container depends on all the git servers being up, but if you have
the uber blob source code you can read the Dockerfile to see what it does.
# Go Environment
Part of the build scripts involve building and running the `doodad` command
from this repo in order to generate the game's built-in doodads. For this to
work smoothly from your Linux or macOS build environment, you may need to
ensure that your `${GOPATH}/bin` directory is on your `$PATH` by, for example,
configuring this in your bash/zsh profile:
```bash
export GOPATH="${HOME}/go"
export PATH="${PATH}:${GOPATH}/bin"
```
For a complete example, see the "Build on macOS from scratch" section below.
# Quickstart with bootstrap.py
From any Unix-like system (Fedora, Ubuntu, macOS) the bootstrap.py script
@ -278,6 +302,47 @@ cp /usr/x86_64-w64-mingw32/bin/SDL*.dll bin/
SDL2_ttf requires libfreetype, you can get its DLL here:
https://github.com/ubawurinna/freetype-windows-binaries
## Build on macOS from scratch
Here are some detailed instructions how to build Sketchy Maze from a fresh
install of macOS Ventura that assumes no previous software or configuration
was applied to the system yet.
Install homebrew: https://brew.sh pay attention to the instructions at the end
of the install to set up your zsh profile for homebrew to work correctly.
Clone the doodle repository:
```bash
git clone https://git.kirsle.net/SketchyMaze/doodle
cd doodle
```
Note: on a fresh install, invoking the `git` command may cause macOS to install
developer tools and Xcode. After installed, run the git clone again to finish
cloning the repository.
Set your Go environment variables: edit your ~/.zprofile and ensure that $GOPATH
is configured and that your $PATH includes $GOPATH/bin. **Note:** restart your
terminal session or reload the config file (e.g. `. ~/.zprofile`) after making
this change.
```bash
# in your .zprofile, .bash_profile, .zshrc or similar shell config
export GOPATH="${HOME}/go"
export PATH="${PATH}:${GOPATH}/bin"
```
Run the bootstrap script:
```bash
python3 bootstrap.py
```
Answer N (default) when asked to clone dependency repos over ssh. The bootstrap
script will `brew install` any necessary dependencies (Go, SDL2, etc.) and clone
support repos for the game (doodads, levelpacks, assets).
# WebAssembly
There is some **experimental** support for a WebAssembly build of Sketchy Maze
@ -304,33 +369,33 @@ Some tips to get a WASM build to work:
* Run `make wasm` to build the WASM binary and `make wasm-serve` to run a simple
Go web server to serve it from.
# Old Docs
# Build Tags
## Build Tags
Go build tags used by this game:
These aren't really used much anymore but documented here:
## dpp
### shareware
The dpp tag stands for Doodle++ and is used for official commercial builds of
the game. Doodle++ builds include additional code not found in the free & open
source release of the game engine.
> Files ending with `_free.go` are for the shareware release as opposed to
> `_paid.go` for the full version.
This build tag should be set automatically by the Makefile **if** the deps/
folder has a git clone of the dpp project. The bootstrap.py script will clone
the dpp repo **if** you use SSH to clone dependencies: so you will need SSH
credentials to the upstream git server. It basically means that third-party
users who download the open source release will not have the dpp dependency,
and will not build dpp copies of the game.
Builds the game in the free shareware release mode.
If you _do_ have the dpp dependency, you can force build (and run) FOSS
versions of the game via the Makefile commands `make build-free`,
`make run-free` or `make dist-free` which are counterparts to the main make
commands but which deliberately do not set the dpp build tag.
Run `make build-free` to build the shareware binary.
In source code, files ending with `_dpp.go` and `_foss.go` are conditionally
compiled depending on this build tag.
Shareware releases of the game have the following changes compared to the default
(release) mode:
How to tell whether your build of Sketchy Maze is Doodle++ include:
* No access to the Doodad Editor scene in-game (soft toggle)
### developer
> Files ending with `_developer.go` are for the developer build as opposed to
> `_release.go` for the public version.
Developer builds support extra features over the standard release version:
* Ability to write the JSON file format for Levels and Doodads.
Run `make build-debug` to build a developer version of the program.
* The version string on the title screen.
* FOSS builds (not dpp) will say "open source" in the version.
* DPP builds may say "shareware" if unregistered or just the version.

View File

@ -9,6 +9,12 @@ CURDIR=$(shell curdir)
LDFLAGS := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE)"
LDFLAGS_W := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE) -H windowsgui"
# Doodle++ build tag for official builds of the game.
BUILD_TAGS := -tags=""
ifneq ("$(wildcard ./deps/dpp)", "")
BUILD_TAGS = -tags="dpp"
endif
# `make setup` to set up a new environment, pull dependencies, etc.
.PHONY: setup
setup: clean
@ -17,8 +23,8 @@ setup: clean
# `make build` to build the binary.
.PHONY: build
build:
go build $(LDFLAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -o bin/doodad cmd/doodad/main.go
go build $(LDFLAGS) $(BUILD_TAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) $(BUILD_TAGS) -o bin/doodad cmd/doodad/main.go
# `make buildall` to run all build steps including doodads.
.PHONY: buildall
@ -28,15 +34,8 @@ buildall: doodads build
.PHONY: build-free
build-free:
gofmt -w .
go build $(LDFLAGS) -tags="shareware" -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags="shareware" -o bin/doodad cmd/doodad/main.go
# `make build-debug` to build the binary in developer mode.
.PHONY: build-debug
build-debug:
gofmt -w .
go build $(LDFLAGS) -tags="developer" -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags="developer" -o bin/doodad cmd/doodad/main.go
go build $(LDFLAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -o bin/doodad cmd/doodad/main.go
# `make bindata` generates the embedded binary assets package.
.PHONY: bindata
@ -74,30 +73,30 @@ doodads:
mingw:
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) -i -o bin/sketchymaze.exe cmd/doodle/main.go
go build $(LDFLAGS_W) $(BUILD_TAGS) -i -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -i -o bin/doodad.exe cmd/doodad/main.go
go build $(LDFLAGS) $(BUILD_TAGS) -i -o bin/doodad.exe cmd/doodad/main.go
# `make mingw32` to cross-compile a Windows binary with mingw (32-bit).
.PHONY: mingw32
mingw32:
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) -i -o bin/sketchymaze.exe cmd/doodle/main.go
go build $(LDFLAGS_W) $(BUILD_TAGS) -i -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -i -o bin/doodad.exe cmd/doodad/main.go
go build $(LDFLAGS) $(BUILD_TAGS) -i -o bin/doodad.exe cmd/doodad/main.go
# `make mingw-free` for Windows binary in free mode.
.PHONY: mingw-free
mingw-free:
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) -tags="shareware" -i -o bin/sketchymaze.exe cmd/doodle/main.go
go build $(LDFLAGS_W) -i -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodad.exe cmd/doodad/main.go
go build $(LDFLAGS) -i -o bin/doodad.exe cmd/doodad/main.go
# `make release` runs the release.sh script, must be run
# after `make dist`
@ -145,12 +144,17 @@ from-docker32: build mingw32 __dist-common
# `make run` to run it from source.
.PHONY: run
run:
go run ${BUILD_TAGS} cmd/doodle/main.go
# `make run-free` to run it from source with no build tags (foss version).
.PHONY: run-free
run-free:
go run cmd/doodle/main.go
# `make debug` to run it in -debug mode.
.PHONY: debug
debug:
go run cmd/doodle/main.go -debug
go run $(BUILD_TAGS) cmd/doodle/main.go -debug
# `make guitest` to run it in guitest mode.
.PHONY: guitest

View File

@ -37,6 +37,10 @@ repos_github = {
"git@github.com:kirsle/audio": "audio",
# TODO: the rest
}
repos_ssh = {
# SSH-only (private) repos.
"git@git.kirsle.net:SketchyMaze/dpp": "dpp",
}
# Software dependencies.
dep_fedora = ["make", "golang", "SDL2-devel", "SDL2_ttf-devel", "SDL2_mixer-devel", "zip", "rsync"]
@ -167,4 +171,8 @@ if __name__ == "__main__":
https = k.replace("git@git.kirsle.net:", "https://git.kirsle.net/")
repos[https] = repos[k]
del repos[k]
else:
# mix in SSH-only repos
repos.update(repos_ssh)
main()

View File

@ -155,7 +155,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
if doodad.Title == "" {
doodad.Title = "Converted Doodad"
}
doodad.Author = native.DefaultAuthor()
doodad.Author = native.DefaultAuthor
// Write the first layer and gather its palette.
log.Info("Converting first layer to drawing and getting the palette")
@ -195,7 +195,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
if lvl.Title == "" {
lvl.Title = "Converted Level"
}
lvl.Author = native.DefaultAuthor()
lvl.Author = native.DefaultAuthor
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
lvl.Palette = palette
lvl.Chunker = chunker

View File

@ -8,9 +8,9 @@ import (
"time"
"git.kirsle.net/SketchyMaze/doodle/cmd/doodad/commands"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
"github.com/urfave/cli/v2"
)
@ -27,19 +27,15 @@ func init() {
}
func main() {
bootstrap.InitPlugins()
app := cli.NewApp()
app.Name = "doodad"
app.Usage = "command line interface for Doodle"
var freeLabel string
if !license.IsRegistered() {
freeLabel = " (shareware)"
}
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
branding.Version,
app.Version = fmt.Sprintf("%s build %s. Built on %s",
builds.Version,
Build,
freeLabel,
BuildDate,
)

View File

@ -1,8 +1,8 @@
package command
import (
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)

View File

@ -4,8 +4,8 @@ import (
"fmt"
"io/ioutil"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)

View File

@ -6,8 +6,8 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/license/levelsigning"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
"github.com/urfave/cli/v2"
)

View File

@ -4,8 +4,8 @@ import (
"io/ioutil"
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)

View File

@ -5,9 +5,9 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/license/levelsigning"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
"github.com/urfave/cli/v2"
)

View File

@ -15,11 +15,13 @@ import (
doodle "git.kirsle.net/SketchyMaze/doodle/pkg"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/chatbot"
"git.kirsle.net/SketchyMaze/doodle/pkg/gamepad"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/sound"
"git.kirsle.net/SketchyMaze/doodle/pkg/sprites"
@ -51,15 +53,12 @@ func init() {
func main() {
runtime.LockOSThread()
bootstrap.InitPlugins()
app := cli.NewApp()
app.Name = "doodle"
app.Usage = fmt.Sprintf("%s - %s", branding.AppName, branding.Summary)
var freeLabel string
if !license.IsRegistered() {
freeLabel = " (shareware)"
}
// Load user settings from disk ASAP.
if err := usercfg.Load(); err != nil {
log.Error("Error loading user settings (defaults will be used): %s", err)
@ -73,10 +72,9 @@ func main() {
// Set GameController style.
gamepad.SetStyle(gamepad.Style(usercfg.Current.ControllerStyle))
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
branding.Version,
app.Version = fmt.Sprintf("%s build %s. Built on %s",
builds.Version,
Build,
freeLabel,
BuildDate,
)
@ -119,6 +117,13 @@ func main() {
}
app.Action = func(c *cli.Context) error {
log.Info("Starting %s %s", app.Name, app.Version)
// Print registration information, + also this sets the DefaultAuthor field.
if reg, err := dpp.Driver.GetRegistration(); err == nil {
log.Info("Registered to %s", reg.Name)
}
// --chdir into a different working directory? e.g. for Flatpak especially.
if doodlePath := c.String("chdir"); doodlePath != "" {
if err := os.Chdir(doodlePath); err != nil {

View File

@ -1,7 +0,0 @@
//go:build shareware
// +build shareware
package balance
// FreeVersion is true in the free version of the game.
const FreeVersion = true

View File

@ -1,7 +0,0 @@
//go:build !shareware
// +build !shareware
package balance
// FreeVersion is true in the free version of the game.
const FreeVersion = false

7
pkg/balance/tag_dpp.go Normal file
View File

@ -0,0 +1,7 @@
//go:build dpp
// +build dpp
package balance
// Doodle++ tag compiled in.
const DPP = true

7
pkg/balance/tag_foss.go Normal file
View File

@ -0,0 +1,7 @@
//go:build !dpp
// +build !dpp
package balance
// Doodle++ tag compiled in.
const DPP = false

View File

@ -0,0 +1,36 @@
// Package builds handles build-specific branding strings.
package builds
import (
"fmt"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
)
var (
/*
Version string for user display.
It may look like the following:
- "v1.2.3 (open source)" for FOSS builds of the game.
- "v1.2.3 (shareware)" for unregistered Doodle++ builds.
- "v1.2.3" for registered Doodle++ builds.
*/
Version = branding.Version
VersionSuffix = " (unknown)"
)
func init() {
if !balance.DPP {
VersionSuffix = " (open source)"
} else if !dpp.Driver.IsRegistered() {
VersionSuffix = " (shareware)"
} else {
VersionSuffix = " (registered)"
}
Version = fmt.Sprintf("v%s%s", branding.Version, VersionSuffix)
}

View File

@ -9,11 +9,9 @@ import (
"strings"
"git.kirsle.net/SketchyMaze/doodle/assets"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/filesystem"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/SketchyMaze/doodle/pkg/wasm"
@ -114,27 +112,6 @@ func ListBuiltin() ([]string, error) {
return result, nil
}
/*
LoadFromEmbeddable reads a doodad file, checking a level's embeddable
file data in addition to the usual places.
Use a true value for `force` to always return the file if available. By
default it will do a license check and free versions of the game won't
read the asset and get an error instead. A "Signed Level" is allowed to
use embedded assets in free versions and the caller uses force=true to
communicate the signature status.
*/
func LoadFromEmbeddable(filename string, fs filesystem.Embeddable, force bool) (*Doodad, error) {
if bin, err := fs.GetFile(balance.EmbeddedDoodadsBasePath + filename); err == nil {
log.Debug("doodads.LoadFromEmbeddable: found %s", filename)
if !force && !license.IsRegistered() {
return nil, license.ErrRegisteredFeature
}
return Deserialize(filename, bin)
}
return LoadFile(filename)
}
// LoadFile reads a doodad file from disk, checking a few locations.
//
// It checks for embedded bindata, system-level doodads on the filesystem,

View File

@ -15,16 +15,17 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/level/giant_screenshot"
"git.kirsle.net/SketchyMaze/doodle/pkg/level/publishing"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui"
)
// EditorScene manages the "Edit Level" game mode.
@ -55,6 +56,8 @@ type EditorScene struct {
filename string
lastAutosaveAt time.Time
winOpenLevel *ui.Window
}
// Name of the scene.
@ -252,7 +255,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
func (s *EditorScene) installActors() error {
if err := s.UI.Canvas.InstallActors(s.Level.Actors); err != nil {
summary := "This level references some doodads that were not found:"
if strings.Contains(err.Error(), license.ErrRegisteredFeature.Error()) {
if strings.Contains(err.Error(), plus.ErrRegisteredFeature.Error()) {
summary = "This level contains embedded doodads, but this is not\n" +
"available in the free version of the game. The following\n" +
"doodads could not be loaded:"
@ -507,7 +510,7 @@ func (s *EditorScene) LoadLevel(filename string) error {
log.Info("Installing %d actors into the drawing", len(level.Actors))
if err := s.UI.Canvas.InstallActors(level.Actors); err != nil {
summary := "This level references some doodads that were not found:"
if strings.Contains(err.Error(), license.ErrRegisteredFeature.Error()) {
if strings.Contains(err.Error(), plus.ErrRegisteredFeature.Error()) {
summary = "This level contains embedded doodads, but this is not\n" +
"available in the free version of the game. The following\n" +
"doodads could not be loaded:"
@ -536,7 +539,7 @@ func (s *EditorScene) SaveLevel(filename string) error {
m.Title = "Alpha"
}
if m.Author == "" {
m.Author = native.DefaultAuthor()
m.Author = native.DefaultAuthor
}
m.Palette = s.UI.Canvas.Palette
@ -638,7 +641,7 @@ func (s *EditorScene) SaveDoodad(filename string) error {
d.Title = "Untitled Doodad"
}
if d.Author == "" {
d.Author = native.DefaultAuthor()
d.Author = native.DefaultAuthor
}
// TODO: is this copying necessary?

View File

@ -6,11 +6,11 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/uix"
@ -595,12 +595,8 @@ func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
}
}
var shareware string
if !license.IsRegistered() {
shareware = " (shareware)"
}
extraLabel := ui.NewLabel(ui.Label{
Text: fmt.Sprintf("%s v%s%s", branding.AppName, branding.Version, shareware),
Text: fmt.Sprintf("%s %s", branding.AppName, builds.Version),
Font: balance.StatusFont,
})
extraLabel.Configure(ui.Config{

View File

@ -9,6 +9,7 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/uix"
"git.kirsle.net/go/render"
)
@ -34,7 +35,7 @@ func (u *EditorUI) startDragActor(doodad *doodads.Doodad, actor *level.Actor) {
if doodad == nil {
if actor != nil {
obj, err := doodads.LoadFromEmbeddable(actor.Filename, u.Scene.Level, false)
obj, err := dpp.Driver.LoadFromEmbeddable(actor.Filename, u.Scene.Level, false)
if err != nil {
log.Error("startDragExistingActor: actor doodad name %s not found: %s", actor.Filename, err)
return

View File

@ -9,10 +9,11 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/level/giant_screenshot"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
"git.kirsle.net/go/render"
@ -111,9 +112,11 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
levelMenu.AddItemAccel("Playtest", "P", func() {
u.Scene.Playtest()
})
levelMenu.AddItem("Publish", func() {
u.OpenPublishWindow()
})
if balance.DPP {
levelMenu.AddItem("Publish", func() {
u.OpenPublishWindow()
})
}
levelMenu.AddSeparator()
levelMenu.AddItem("Screenshot", func() {
@ -276,17 +279,22 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
////////
// Help menu
var (
helpMenu = u.d.MakeHelpMenu(menu, u.Supervisor)
registerText = "Register"
helpMenu = u.d.MakeHelpMenu(menu, u.Supervisor)
)
helpMenu.AddSeparator()
if license.IsRegistered() {
registerText = "Registration"
// Registration item for Doodle++ builds.
if balance.DPP {
var registerText = "Register"
if dpp.Driver.IsRegistered() {
registerText = "Registration"
}
helpMenu.AddSeparator()
helpMenu.AddItem(registerText, func() {
u.licenseWindow.Show()
u.Supervisor.FocusWindow(u.licenseWindow)
})
}
helpMenu.AddItem(registerText, func() {
u.licenseWindow.Show()
u.Supervisor.FocusWindow(u.licenseWindow)
})
menu.Supervise(u.Supervisor)
menu.Compute(d.Engine)
@ -313,7 +321,24 @@ func (s *EditorScene) MenuNewDoodad() {
// File->Open, or Ctrl-O
func (s *EditorScene) MenuOpen() {
s.ConfirmUnload(func() {
s.d.GotoLoadMenu()
if s.winOpenLevel == nil {
s.winOpenLevel = windows.NewOpenDrawingWindow(windows.OpenDrawing{
Supervisor: s.UI.Supervisor,
Engine: shmem.CurrentRenderEngine,
OnOpenDrawing: func(filename string) {
s.d.EditFile(filename)
},
OnCloseWindow: func() {
s.winOpenLevel.Destroy()
s.winOpenLevel = nil
},
})
}
s.winOpenLevel.MoveTo(render.Point{
X: (s.d.width / 2) - (s.winOpenLevel.Size().W / 2),
Y: (s.d.height / 2) - (s.winOpenLevel.Size().H / 2),
})
s.winOpenLevel.Show()
})
}

View File

@ -9,9 +9,9 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
@ -68,7 +68,7 @@ func (u *EditorUI) OpenPublishWindow() {
OnPublish: func(includeBuiltins bool) {
u.d.FlashError("OnPublish Called")
// XXX: Paid Version Only.
if !license.IsRegistered() {
if !dpp.Driver.IsRegistered() {
if u.licenseWindow != nil {
u.licenseWindow.Show()
u.Supervisor.FocusWindow(u.licenseWindow)

View File

@ -9,9 +9,9 @@ import (
"path/filepath"
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/SketchyMaze/doodle/pkg/wallpaper"
@ -95,7 +95,7 @@ func GiantScreenshot(lvl *level.Level) (image.Image, error) {
// Render the doodads.
log.Debug("GiantScreenshot: Render actors...")
for _, actor := range lvl.Actors {
doodad, err := doodads.LoadFromEmbeddable(actor.Filename, lvl, false)
doodad, err := dpp.Driver.LoadFromEmbeddable(actor.Filename, lvl, false)
if err != nil {
log.Error("GiantScreenshot: Load doodad: %s", err)
continue

View File

@ -12,9 +12,9 @@ import (
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/go/render"
"golang.org/x/image/draw"
@ -78,7 +78,7 @@ func CroppedScreenshot(lvl *level.Level, viewport render.Rect) (image.Image, err
// Render the doodads.
log.Debug("CroppedScreenshot: Render actors...")
for _, actor := range lvl.Actors {
doodad, err := doodads.LoadFromEmbeddable(actor.Filename, lvl, false)
doodad, err := dpp.Driver.LoadFromEmbeddable(actor.Filename, lvl, false)
if err != nil {
log.Error("CroppedScreenshot: Load doodad: %s", err)
continue

View File

@ -17,8 +17,8 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
)
/*
@ -37,7 +37,7 @@ func Publish(lvl *level.Level) error {
}
// Registered games only.
if !license.IsRegistered() {
if !balance.DPP || !dpp.Driver.IsRegistered() {
return errors.New("only registered versions of the game can attach doodads to levels")
}
@ -52,7 +52,7 @@ func Publish(lvl *level.Level) error {
log.Debug("Embed filename: %s", filename)
names[filename] = nil
doodad, err := doodads.LoadFromEmbeddable(filename, lvl, false)
doodad, err := dpp.Driver.LoadFromEmbeddable(filename, lvl, false)
if err != nil {
return fmt.Errorf("couldn't load doodad %s: %s", filename, err)
}

View File

@ -91,7 +91,7 @@ func New() *Level {
Base: Base{
Version: 1,
Title: "Untitled",
Author: native.DefaultAuthor(),
Author: native.DefaultAuthor,
Files: NewFileSystem(),
},
Chunker: NewChunker(balance.ChunkSize),

View File

@ -1,100 +0,0 @@
package license
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"time"
"github.com/dgrijalva/jwt-go"
)
// AdminGenerateKeys generates the ECDSA public and private key pair for the admin
// side of creating signed license files.
func AdminGenerateKeys() (*ecdsa.PrivateKey, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
return privateKey, err
}
// AdminWriteKeys writes the admin signing key to .pem files on disk.
func AdminWriteKeys(key *ecdsa.PrivateKey, privateFile, publicFile string) error {
// Encode the private key to PEM format.
x509Encoded, err := x509.MarshalECPrivateKey(key)
if err != nil {
return err
}
pemEncoded := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: x509Encoded,
})
// Encode the public key to PEM format.
x509EncodedPub, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
return err
}
pemEncodedPub := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: x509EncodedPub,
})
// Write the files.
if err := ioutil.WriteFile(privateFile, pemEncoded, 0600); err != nil {
return err
}
if err := ioutil.WriteFile(publicFile, pemEncodedPub, 0644); err != nil {
return err
}
return nil
}
// AdminLoadPrivateKey loads the private key from disk.
func AdminLoadPrivateKey(privateFile string) (*ecdsa.PrivateKey, error) {
// Read the private key file.
pemEncoded, err := ioutil.ReadFile(privateFile)
if err != nil {
return nil, err
}
// Decode the private key.
block, _ := pem.Decode([]byte(pemEncoded))
x509Encoded := block.Bytes
privateKey, _ := x509.ParseECPrivateKey(x509Encoded)
return privateKey, nil
}
// AdminLoadPublicKey loads the private key from disk.
func AdminLoadPublicKey(publicFile string) (*ecdsa.PublicKey, error) {
pemEncodedPub, err := ioutil.ReadFile(publicFile)
if err != nil {
return nil, err
}
// Decode the public key.
blockPub, _ := pem.Decode([]byte(pemEncodedPub))
x509EncodedPub := blockPub.Bytes
genericPublicKey, _ := x509.ParsePKIXPublicKey(x509EncodedPub)
publicKey := genericPublicKey.(*ecdsa.PublicKey)
return publicKey, nil
}
// AdminSignRegistration signs the registration object.
func AdminSignRegistration(key *ecdsa.PrivateKey, reg Registration) (string, error) {
reg.StandardClaims = jwt.StandardClaims{
Issuer: "Maze Admin",
IssuedAt: time.Now().Unix(),
NotBefore: time.Now().Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodES384, reg)
signed, err := token.SignedString(key)
if err != nil {
return "", err
}
return signed, nil
}

View File

@ -1,25 +0,0 @@
package license
import (
"crypto/ecdsa"
"fmt"
)
// Run-time configuration variables provided by the application.
const pemSigningKey = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMrqAMHjZ1dPlKwDOsiCSr5N3OSvnYKLM
efe2xD+5hJYrpvparRFnaMbMuqde4M6d6sCCKO8BHtfAzmyiQ/CD38zs9MiDsamy
FDYEEJu+Fqx482I7fIa5ZEE770+wWJ3k
-----END PUBLIC KEY-----`
var Signer *ecdsa.PublicKey
func init() {
key, err := ParsePublicKeyPEM(pemSigningKey)
if err != nil {
fmt.Printf("license: failed to parse app keys: %s\n", err)
return
}
Signer = key
}

View File

@ -1,193 +0,0 @@
package levelsigning
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"fmt"
"io/ioutil"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
)
// IsLevelSigned returns a quick answer.
func IsLevelSigned(lvl *level.Level) bool {
return VerifyLevel(license.Signer, lvl)
}
// IsLevelPackSigned returns a quick answer.
func IsLevelPackSigned(lp *levelpack.LevelPack) bool {
return VerifyLevelPack(license.Signer, lp)
}
/*
SignLevel creates a signature on a level file which allows it to load its
embedded doodads even for free versions of the game.
Free versions will verify a level's signature before bailing out with the
"can't play levels w/ embedded doodads" response.
NOTE: this only supported Zipfile levels and will assume the level you
pass has a Zipfile to access embedded assets.
*/
func SignLevel(key *ecdsa.PrivateKey, lvl *level.Level) ([]byte, error) {
// Encode the attached files data to deterministic JSON.
certificate, err := StringifyAssets(lvl)
if err != nil {
return nil, err
}
log.Info("Sign file tree: %s", certificate)
digest := shasum(certificate)
signature, err := ecdsa.SignASN1(rand.Reader, key, digest)
if err != nil {
return nil, err
}
log.Info("Digest: %x Signature: %x", digest, signature)
return signature, nil
}
// VerifyLevel verifies a level's signature and returns if it is OK.
func VerifyLevel(publicKey *ecdsa.PublicKey, lvl *level.Level) bool {
// No signature = not verified.
if lvl.Signature == nil || len(lvl.Signature) == 0 {
return false
}
// Encode the attached files data to deterministic JSON.
certificate, err := StringifyAssets(lvl)
if err != nil {
log.Error("VerifyLevel: couldn't stringify assets: %s", err)
return false
}
digest := shasum(certificate)
// Verify the signature against our public key.
return ecdsa.VerifyASN1(publicKey, digest, lvl.Signature)
}
/*
SignLevelpack applies a signature to a levelpack as a whole, to allow its
shared custom doodads to be loaded by its levels in free games.
*/
func SignLevelPack(key *ecdsa.PrivateKey, lp *levelpack.LevelPack) ([]byte, error) {
// Encode the attached files data to deterministic JSON.
certificate, err := StringifyLevelpackAssets(lp)
if err != nil {
return nil, err
}
log.Info("Sign file tree: %s", certificate)
digest := shasum(certificate)
signature, err := ecdsa.SignASN1(rand.Reader, key, digest)
if err != nil {
return nil, err
}
log.Info("Digest: %x Signature: %x", digest, signature)
return signature, nil
}
// VerifyLevelPack verifies a levelpack's signature and returns if it is OK.
func VerifyLevelPack(publicKey *ecdsa.PublicKey, lp *levelpack.LevelPack) bool {
// No signature = not verified.
if lp.Signature == nil || len(lp.Signature) == 0 {
return false
}
// Encode the attached files data to deterministic JSON.
certificate, err := StringifyLevelpackAssets(lp)
if err != nil {
log.Error("VerifyLevelPack: couldn't stringify assets: %s", err)
return false
}
digest := shasum(certificate)
// Verify the signature against our public key.
return ecdsa.VerifyASN1(publicKey, digest, lp.Signature)
}
// StringifyAssets creates the signing checksum of a level's attached assets.
func StringifyAssets(lvl *level.Level) ([]byte, error) {
// Get a listing of all embedded files. Note: gives us a conveniently
// sorted array of files too.
files := lvl.Files.List()
// Pair each filename with its SHA256 sum.
var checksum = map[string]string{}
for _, filename := range files {
if sum, err := lvl.Files.Checksum(filename); err != nil {
return nil, fmt.Errorf("when checksum %s got error: %s", filename, err)
} else {
checksum[filename] = sum
}
}
// Encode the payload to deterministic JSON.
certificate, err := json.Marshal(checksum)
if err != nil {
return nil, err
}
return certificate, nil
}
// StringifyLevelpackAssets creates the signing checksum of a level's attached assets.
func StringifyLevelpackAssets(lp *levelpack.LevelPack) ([]byte, error) {
var (
files = []string{}
seen = map[string]struct{}{}
)
// Enumerate the files in the zipfile assets/ folder.
for _, file := range lp.Zipfile.File {
if file.Name == "index.json" {
continue
}
if _, ok := seen[file.Name]; !ok {
files = append(files, file.Name)
seen[file.Name] = struct{}{}
}
}
// Pair each filename with its SHA256 sum.
var checksum = map[string]string{}
for _, filename := range files {
file, err := lp.Zipfile.Open(filename)
if err != nil {
return nil, err
}
bin, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
checksum[filename] = fmt.Sprintf("%x", shasum(bin))
}
// Encode the payload to deterministic JSON.
certificate, err := json.Marshal(checksum)
if err != nil {
return nil, err
}
return certificate, nil
}
// Common function to SHA-256 checksum a thing.
func shasum(data []byte) []byte {
h := sha256.New()
h.Write(data)
return h.Sum(nil)
}

View File

@ -1,108 +0,0 @@
// Package license holds functions related to paid product activation.
package license
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"path/filepath"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"github.com/dgrijalva/jwt-go"
)
// Errors
var (
ErrRegisteredFeature = errors.New("feature not available")
)
// Registration object encoded into a license key file.
type Registration struct {
Name string `json:"name"`
Email string `json:"email"`
jwt.StandardClaims
}
// IsRegistered returns a boolean answer: is the product registered?
func IsRegistered() bool {
if _, err := GetRegistration(); err == nil {
return true
}
return false
}
// GetRegistration returns the currently registered user, by checking
// for the license.key file in the profile folder.
func GetRegistration() (Registration, error) {
if Signer == nil {
return Registration{}, errors.New("signer not ready")
}
filename := filepath.Join(userdir.ProfileDirectory, "license.key")
jwt, err := ioutil.ReadFile(filename)
if err != nil {
return Registration{}, err
}
// Check if the JWT is valid.
reg, err := Validate(Signer, string(jwt))
if err != nil {
return Registration{}, err
}
return reg, err
}
// UploadLicenseFile handles the user selecting the license key file, and it is
// validated and ingested.
func UploadLicenseFile(filename string) (Registration, error) {
if Signer == nil {
return Registration{}, errors.New("signer not ready")
}
jwt, err := ioutil.ReadFile(filename)
if err != nil {
return Registration{}, err
}
// Check if the JWT is valid.
reg, err := Validate(Signer, string(jwt))
if err != nil {
return Registration{}, err
}
// Upload the license to Doodle's profile directory.
outfile := filepath.Join(userdir.ProfileDirectory, "license.key")
if err := ioutil.WriteFile(outfile, jwt, 0644); err != nil {
return Registration{}, err
}
return reg, nil
}
// Validate the registration is signed by the appropriate public key.
func Validate(publicKey *ecdsa.PublicKey, tokenString string) (Registration, error) {
var reg Registration
token, err := jwt.ParseWithClaims(tokenString, &reg, func(token *jwt.Token) (interface{}, error) {
return publicKey, nil
})
if err != nil {
return reg, err
}
if !token.Valid {
return reg, errors.New("token not valid")
}
return reg, nil
}
// ParsePublicKeyPEM loads a public key from PEM format.
func ParsePublicKeyPEM(keytext string) (*ecdsa.PublicKey, error) {
blockPub, _ := pem.Decode([]byte(keytext))
x509EncodedPub := blockPub.Bytes
genericPublicKey, _ := x509.ParsePKIXPublicKey(x509EncodedPub)
publicKey := genericPublicKey.(*ecdsa.PublicKey)
return publicKey, nil
}

View File

@ -6,12 +6,13 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/savegame"
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
@ -120,12 +121,8 @@ func (s *MainScene) Setup(d *Doodle) error {
s.labelSubtitle.Compute(d.Engine)
// Version label.
var shareware string
if !license.IsRegistered() {
shareware = " (shareware)"
}
ver := ui.NewLabel(ui.Label{
Text: fmt.Sprintf("v%s%s", branding.Version, shareware),
Text: builds.Version,
Font: balance.TitleScreenVersionFont,
})
ver.Compute(d.Engine)
@ -228,7 +225,7 @@ func (s *MainScene) Setup(d *Doodle) error {
{
Name: "Register",
If: func() bool {
return !license.IsRegistered()
return balance.DPP && !dpp.Driver.IsRegistered()
},
Func: func() {
if s.winRegister == nil {

View File

@ -73,6 +73,8 @@ func (d *Doodle) GotoNewDoodadMenu() {
}
// GotoLoadMenu loads the MenuScene and shows the "Load" window.
//
// DEPRECATED: loads the old menu, in dev console run `$ d.GotoLoadMenu()` to see.
func (d *Doodle) GotoLoadMenu() {
log.Info("Loading the MenuScene to the Load window for Edit Mode")
scene := &MenuScene{
@ -83,6 +85,8 @@ func (d *Doodle) GotoLoadMenu() {
// GotoPlayMenu loads the MenuScene and shows the "Load" window for playing a
// level, not editing it.
//
// DEPRECATED: loads the old menu, in dev console run `$ d.GotoPlayMenu()` to see.
func (d *Doodle) GotoPlayMenu() {
log.Info("Loading the MenuScene to the Load window for Play Mode")
scene := &MenuScene{

View File

@ -2,28 +2,9 @@ package native
import (
"os"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
)
var USER string = os.Getenv("USER")
/*
DefaultAuthor will return the local user's name to be the default Author
for levels and doodads they create.
If they have registered the game, use the name from their license JWT token.
Otherwise fall back to their native operating system user.
*/
func DefaultAuthor() string {
// Are we registered?
if license.IsRegistered() {
if reg, err := license.GetRegistration(); err == nil {
return reg.Name
}
}
// Return OS username
return os.Getenv("USER")
}
var (
USER string = os.Getenv("USER")
DefaultAuthor = USER
)

View File

@ -13,12 +13,12 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/keybind"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/license/levelsigning"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
"git.kirsle.net/SketchyMaze/doodle/pkg/physics"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/savegame"
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
@ -62,6 +62,7 @@ type PlayScene struct {
menubar *ui.MenuBar
editButton *ui.Button
winLevelPacks *ui.Window
winOpenLevel *ui.Window
// Custom debug labels.
debPosition *string
@ -246,7 +247,7 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
s.drawing.OnResetTimer = s.ResetTimer
// If this level game from a signed LevelPack, inform the canvas.
if s.LevelPack != nil && levelsigning.IsLevelPackSigned(s.LevelPack) {
if s.LevelPack != nil && dpp.Driver.IsLevelPackSigned(s.LevelPack) {
s.drawing.IsSignedLevelPack = s.LevelPack
}
@ -336,7 +337,7 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
func (s *PlayScene) installActors() error {
if err := s.drawing.InstallActors(s.Level.Actors); err != nil {
summary := "This level references some doodads that were not found:"
if strings.Contains(err.Error(), license.ErrRegisteredFeature.Error()) {
if strings.Contains(err.Error(), plus.ErrRegisteredFeature.Error()) {
summary = "This level contains embedded doodads, but this is not\n" +
"available in the free version of the game. The following\n" +
"doodads could not be loaded:"
@ -503,7 +504,7 @@ func (s *PlayScene) setupPlayer(playerCharacterFilename string) {
// centerIn is optional, ignored if zero.
func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, centerIn render.Rect) {
// Load in the player character.
player, err := doodads.LoadFromEmbeddable(filename, s.Level, false)
player, err := dpp.Driver.LoadFromEmbeddable(filename, s.Level, false)
if err != nil {
log.Error("PlayScene.Setup: failed to load player doodad: %s", err)
player = doodads.NewDummy(32)

View File

@ -40,7 +40,26 @@ func (u *PlayScene) setupMenuBar(d *Doodle) *ui.MenuBar {
u.winLevelPacks.Show()
})
gameMenu.AddItemAccel("New drawing", "Ctrl-N", d.GotoNewMenu)
gameMenu.AddItemAccel("Open drawing", "Ctrl-O", d.GotoLoadMenu)
gameMenu.AddItemAccel("Open drawing", "Ctrl-O", func() {
if u.winOpenLevel == nil {
u.winOpenLevel = windows.NewOpenDrawingWindow(windows.OpenDrawing{
Supervisor: u.Supervisor,
Engine: shmem.CurrentRenderEngine,
OnOpenDrawing: func(filename string) {
d.EditFile(filename)
},
OnCloseWindow: func() {
u.winOpenLevel.Destroy()
u.winOpenLevel = nil
},
})
}
u.winOpenLevel.MoveTo(render.Point{
X: (d.width / 2) - (u.winOpenLevel.Size().W / 2),
Y: (d.height / 2) - (u.winOpenLevel.Size().H / 2),
})
u.winOpenLevel.Show()
})
gameMenu.AddSeparator()
gameMenu.AddItem("Quit to menu", func() {

View File

@ -0,0 +1,18 @@
/*
Package bootstrap is a common import between the Doodle and Doodad programs.
Its chief job is to work around circular dependency issues when dealing with
pluggable parts of the codebase, such as Doodle++ which adds features for the
official release which are missing from the FOSS version.
*/
package bootstrap
import (
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
)
var Driver dpp.Pluggable
func InitPlugins() {
Driver = dpp.Plugin{}
}

24
pkg/plus/dpp/plugin.go Normal file
View File

@ -0,0 +1,24 @@
package dpp
import (
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/filesystem"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
)
// Driver is the currently installed Doodle++ implementation (FOSS or DPP).
var Driver Pluggable
// Pluggable defines the interface for Doodle++ functions, so that their implementations
// can avoid cyclic dependency errors. Documentation for these functions is only spelled
// out in the SketchyMaze/dpp package.
type Pluggable interface {
LoadFromEmbeddable(string, filesystem.Embeddable, bool) (*doodads.Doodad, error)
IsRegistered() bool
GetRegistration() (plus.Registration, error)
UploadLicenseFile(string) (plus.Registration, error)
IsLevelSigned(*level.Level) bool
IsLevelPackSigned(*levelpack.LevelPack) bool
}

77
pkg/plus/dpp/plus_dpp.go Normal file
View File

@ -0,0 +1,77 @@
//go:build dpp
// +build dpp
package dpp
import (
"encoding/json"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/filesystem"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
"git.kirsle.net/SketchyMaze/dpp/embedding"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
)
type Plugin struct{}
func (Plugin) LoadFromEmbeddable(filename string, fs filesystem.Embeddable, force bool) (*doodads.Doodad, error) {
return embedding.LoadFromEmbeddable(filename, fs, force)
}
func (Plugin) IsRegistered() bool {
return license.IsRegistered()
}
func (Plugin) GetRegistration() (plus.Registration, error) {
reg, err := license.GetRegistration()
if err != nil {
return plus.Registration{}, err
}
return translateLicenseStruct(reg)
}
func (Plugin) UploadLicenseFile(filename string) (plus.Registration, error) {
reg, err := license.UploadLicenseFile(filename)
if err != nil {
return plus.Registration{}, err
}
return translateLicenseStruct(reg)
}
// Hack: to translate JWT token types, easiest is to just encode/decode them (inner jwt.StandardClaims complexity).
func translateLicenseStruct(reg license.Registration) (plus.Registration, error) {
// Set the DefaultAuthor to the registered user's name.
if reg.Name != "" {
native.DefaultAuthor = reg.Name
}
// Marshal to JSON and back to cast the type.
var (
result plus.Registration
jsonStr, err = json.Marshal(reg)
)
if err != nil {
return plus.Registration{}, err
}
err = json.Unmarshal(jsonStr, &result)
return result, err
}
func (Plugin) IsLevelPackSigned(lp *levelpack.LevelPack) bool {
return levelsigning.IsLevelPackSigned(lp)
}
func (Plugin) IsLevelSigned(lvl *level.Level) bool {
return levelsigning.IsLevelSigned(lvl)
}
func init() {
Driver = Plugin{}
}

46
pkg/plus/dpp/plus_foss.go Normal file
View File

@ -0,0 +1,46 @@
//go:build !dpp
// +build !dpp
package dpp
import (
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/filesystem"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
)
type Plugin struct{}
func (Plugin) LoadFromEmbeddable(filename string, fs filesystem.Embeddable, force bool) (*doodads.Doodad, error) {
if result, err := doodads.LoadFile(filename); err != nil {
return nil, plus.ErrRegisteredFeature
} else {
return result, nil
}
}
func (Plugin) IsRegistered() bool {
return false
}
func (Plugin) GetRegistration() (plus.Registration, error) {
return plus.Registration{}, plus.ErrNotImplemented
}
func (Plugin) UploadLicenseFile(string) (plus.Registration, error) {
return plus.Registration{}, plus.ErrNotImplemented
}
func (Plugin) IsLevelPackSigned(*levelpack.LevelPack) bool {
return false
}
func (Plugin) IsLevelSigned(*level.Level) bool {
return false
}
func init() {
Driver = Plugin{}
}

21
pkg/plus/plus.go Normal file
View File

@ -0,0 +1,21 @@
// Package plus connects the open source Doodle engine to the Doodle++ feature.
package plus
import (
"errors"
"github.com/dgrijalva/jwt-go"
)
// Errors
var (
ErrNotImplemented = errors.New("not implemented")
ErrRegisteredFeature = errors.New("feature not available")
)
// Registration object encoded into a license key file.
type Registration struct {
Name string `json:"name"`
Email string `json:"email"`
jwt.StandardClaims
}

View File

@ -6,10 +6,9 @@ import (
"sort"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/license/levelsigning"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting"
"git.kirsle.net/SketchyMaze/doodle/pkg/scripting/exceptions"
"git.kirsle.net/go/render"
@ -37,18 +36,18 @@ func (w *Canvas) InstallActors(actors level.ActorMap) error {
// Signed Levels: the free version normally won't load embedded assets from
// a level and the call to LoadFromEmbeddable below returns the error. If the
// level is signed it is allowed to use its embedded assets.
isSigned := w.IsSignedLevelPack != nil || levelsigning.IsLevelSigned(w.level)
isSigned := w.IsSignedLevelPack != nil || dpp.Driver.IsLevelSigned(w.level)
w.actors = make([]*Actor, 0)
for _, id := range actorIDs {
var actor = actors[id]
// Try loading the doodad from the level's own attached files.
doodad, err := doodads.LoadFromEmbeddable(actor.Filename, w.level, isSigned)
doodad, err := dpp.Driver.LoadFromEmbeddable(actor.Filename, w.level, isSigned)
if err != nil {
// If we have a signed levelpack, try loading from the levelpack.
if w.IsSignedLevelPack != nil {
if found, err := doodads.LoadFromEmbeddable(actor.Filename, w.IsSignedLevelPack, true); err == nil {
if found, err := dpp.Driver.LoadFromEmbeddable(actor.Filename, w.IsSignedLevelPack, true); err == nil {
doodad = found
}
}

View File

@ -6,10 +6,12 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
"git.kirsle.net/go/ui/style"
@ -50,12 +52,12 @@ func NewLicenseWindow(cfg License) *ui.Window {
labelSize = render.NewRect(100, 16)
valueSize = render.NewRect(windowWidth-labelSize.W-4, labelSize.H)
isRegistered bool
registration license.Registration
summary = "Unregistered (shareware)"
registration plus.Registration
summary = "Unregistered" + builds.VersionSuffix
)
// Get our current registration status.
if reg, err := license.GetRegistration(); err == nil {
if reg, err := dpp.Driver.GetRegistration(); err == nil {
isRegistered = true
registration = reg
windowHeight = 200
@ -140,7 +142,7 @@ func NewLicenseWindow(cfg License) *ui.Window {
}
// Upload and validate the license key.
reg, err := license.UploadLicenseFile(filename)
reg, err := dpp.Driver.UploadLicenseFile(filename)
if err != nil {
modal.Alert("That license key didn't seem quite right.").WithTitle("License Error")
return

View File

@ -171,9 +171,8 @@ func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window {
* Frame for selecting User Doodads
******************/
// Doodads not shown if we're loading a map to play, nor are they
// available to the free version.
if !config.LoadForPlay && !balance.FreeVersion {
// Doodads not shown if we're loading a map to play.
if !config.LoadForPlay {
label2 := ui.NewLabel(ui.Label{
Text: "Doodads",
Font: balance.LabelFont,