A cross-platform community-driven reimplementation of Plants vs. Zombies: Game of the Year Edition, aiming to bring the 100% authentic experience of Plants vs. Zombies to every platform.
| 🌿 Authentic | 🎮 Portable | 🛠️ Open |
|---|---|---|
| Almost 100% gameplay recreation | Support for 32/64 bit systems Run on Linux, Windows, macOS, Android, Switch... |
OpenGL ES 2.0 & SDL |
- This repository does NOT contain any copyrighted game assets (such as images, music, or fonts) owned by PopCap Games or Electronic Arts. Users must provide their own
main.pakandproperties/folder from a legally purchased copy of Plants vs. Zombies: GOTY Edition. - The codebase is a manual reimplementation derived from publicly available reverse-engineering documentation and community research (such as 植物大战僵尸吧, PVZ Wiki and PvZ Tools). It is written to utilize portable backends like SDL2 and OpenGL ES 2.0 (with desktop OpenGL 2.1 fallback).
- This project is intended solely for educational purposes, focusing on cross-platform porting techniques, engine modernization, and learning how classic game logic can be adapted to various hardware architectures (e.g., Nintendo Switch, 3DS).
- Non-Commercial: This project is not affiliated with, authorized, or endorsed by PopCap Games or Electronic Arts.
- Project icons and platform-specific logos are created by me (wszqkzqk) with the help of AI image generation tools and are not official assets of PopCap/EA.
- To play the game using this project you MUST have access to the original game files by purchasing it on EA's official website or Steam.
This project is based on Patoke and Headshotnoby's PvZ GOTY implementation with the following objectives:
- Replace renderer with SDL + OpenGL ES 2.0 (desktop OpenGL 2.1 fallback)
- Also enable to resize the window, which was not possible in the original game
- Why OpenGL ES 2.0? GLES 2.0 is the common subset of virtually all modern GPU APIs — every desktop OpenGL 2.1+ driver, mobile GPU, and game console inherently supports it. This means the game works out of the box everywhere without extra dependencies. ANGLE can also be optionally used to translate calls to DirectX/Metal/Vulkan if needed.
- Replace Windows code with cross-platform code
- Replace DirectSound/BASS/FMod with SDL Mixer X
- This project uses a fork of SDL Mixer X that adds compatibility with the MO3 format by using libopenmpt. This fork is located under SexyAppFramework/sound/SDL-Mixer-X
- main.pak support
- Optimize memory usage for console ports (Partial)
- Compatible with original PvZ GOTY Edition's global user data (profile info, adventure progress, coins, Zen Garden, etc., stored in
user*.dat)- Fix 2038 year problem while keeping compatibility
- Portable mid-level save game format
.v4support (share mid-level saves between Windows, Linux, macOS, Android, Switch, etc.)- Support export/import of
.v4save files to/from human-readable YAML format for easy editing
- Support export/import of
- Implement with
std::threadfor cross-platform threading support - Implement with
std::filesystemfor cross-platform file system support - Replace wide-string with
std::stringand UTF-8 encoding - Multilingual Support: Supports game resource data from official GOTY editions in various languages, including Chinese, German, Spanish, French, and Italian.
- 32 and 64-bit builds support
- Different CPU architectures support (i686, x86_64, aarch64, riscv64, loongarch64, etc.)
- Unicode path support on all platforms
- Different endianness support (little-endian and big-endian)
- Save data compatibility across endianness
- Theoretically supports big-endian platforms, but untested due to lack of hardware
This project supports the following platforms:
| Platform | Data path | Status |
|---|---|---|
| Linux | Executable dir (resources); per-user app-data for writable files | Works |
| Windows | Executable dir (resources); per-user app-data for writable files | Works |
| macOS | Executable dir (resources); per-user app-data for writable files | Works |
| BSD Family | Executable dir (resources); per-user app-data for writable files | Works (verified at least on FreeBSD) |
| Haiku | Executable dir (resources); per-user app-data for writable files | Partially works: no music |
| Android | Android/data/io.github.wszqkzqk.pvzportable/files/ |
Works |
| Nintendo Switch | sdmc:/switch/PvZPortable | Works on real hardware. Kenji-NX crashes on boot. |
| Nintendo 3DS | sdmc:/3ds/PvZPortable | In development, might not have enough memory for Old 3DS, might barely work on New 3DS |
To play the game, you need the game data from PvZ GOTY. Place main.pak and the properties/ folder next to the pvz-portable executable (the game will search for resources relative to the executable's directory). You can also use extracted data instead of main.pak if you prefer.
Note about writable data and caches:
- The game will read resources (like
main.pakandproperties/) from the executable directory by default, so you can launch the binary from any working directory and it will still find them. - Per-user writable files (settings, savegames, compiled caches, screenshots) are stored in the OS-recommended application data path. With the current build these are under
io.github.wszqkzqk/PvZPortableand include subfolders such as:userdata/— Player save files.cache64/if you use the 64-bit version orcache32/if you use the 32-bit version — Compiled binary caches (reanimation / compiled definitions). These caches are local startup artifacts (native layout), not portable files; when cache/schema checks fail, the game transparently recompiles from source data.registry.regemu— Settings/registry emulation.
Examples:
- Linux:
~/.local/share/io.github.wszqkzqk/PvZPortable/ - Windows:
%APPDATA%\io.github.wszqkzqk\PvZPortable\ - macOS:
~/Library/Application Support/io.github.wszqkzqk/PvZPortable/
You can customize these paths via command-line parameters:
-resdir="<path>": Set the resource directory (wheremain.pakandproperties/are located). This only affects where the game looks for resources, not where it saves data.-savedir="<path>": Set the save data directory (where settings, savegames, caches, and screenshots are stored). This overrides the default OS-recommended application data path.
Note: You MUST use the format -param="<Your Path>". Space-separated values (e.g. -resdir path) are NOT supported.
Download the APK from the Releases page or build it yourself. Because this project does not include any game assets, you will need to import the game resources from a legally purchased copy of Plants vs. Zombies: GOTY Edition.
- Install the APK (you may need to enable "Install from unknown sources").
- Launch the app — since no game resources are found, a resource import screen will appear automatically.
- Import game resources by selecting either:
- A ZIP file containing
main.pakandproperties/(either at the ZIP root or inside one wrapper directory, e.g.PvZ/main.pak). - A folder that directly contains
main.pakandproperties/, or whose immediate subfolder does.
- A ZIP file containing
- Press Start Game once the import succeeds.
Long-press the app icon on your launcher to access the Manage Data shortcut, which opens the resource management screen where you can re-import resources, export saves, or import saves.
- All data is stored in
Android/data/io.github.wszqkzqk.pvzportable/files/. No extra storage permissions are needed — the app uses the Storage Access Framework (SAF) for all imports and exports. - Save data is interchangeable with desktop versions. See the save data section chapter for details.
- The Android port is part of this project's cross-platform porting research. It preserves the original game's 4:3 aspect ratio and mouse-based input model — no touch-screen-specific UI optimizations have been made. SDL2 automatically maps touch events to mouse input, so the game is playable but not designed for mobile ergonomics.
This project is designed and tested against Plants vs. Zombies GOTY Edition 1.2.0.1073 EN (the standalone PopCap release). Non-English GOTY editions (1.2.0.1093 DE/ES/FR/IT or 1.1.0.1056 ZH based on 1.2.0.1073) and the Steam GOTY Edition 1.2.0.1096 are also fully playable — all game mechanics work correctly. The only differences are minor cosmetic UI text issues caused by renamed string keys across versions, and these can be easily fixed by the user via a custom properties/default.xml (see below).
Recommendation: use the 1.2.0.1073 EN asset package for the best out-of-box experience.
Known cosmetic differences with non-1.2.0.1073 EN assets (click to expand)
| Issue (Non-1.2.0.1073 EN only) | Cause |
|---|---|
| Almanac blue description text missing | In 1.2.0.1096, the plain-text introductory paragraph was split out from [XXX_DESCRIPTION] into a new [XXX_DESCRIPTION_HEADER] key. The engine only reads [XXX_DESCRIPTION], so the header text is never displayed. |
| "Restart" button label missing | The key [RESTART_LEVEL] (used for the button label) was renamed to [RESTART_LEVEL_BUTTON] in 1.2.0.1096. |
Unencountered zombie shows ??? instead of (not encountered yet) |
The string [NOT_ENCOUNTERED_YET] changed its value to ??? in 1.2.0.1096; the old text was moved to a new key [NOT_ENCOUNTERED_YET_DESCRIPTION]. |
| Crazy Dave's plant sell price shows 1/10 of the correct value | In 1.2.0.1073, the string template [CRAZY_DAVE_1700] contains a trailing 0 after {SELL_PRICE} (i.e. ${SELL_PRICE}0) because the engine passes the price divided by 10. In 1.2.0.1096 the trailing 0 was removed, so the displayed price becomes 1/10 of the intended amount. |
All of the above can be resolved by adding the missing or corrected string entries to a properties/default.xml file placed alongside the game data.
PvZ-Portable supports game resource data from non-English versions of Plants vs. Zombies GOTY Edition. The engine handles BOM-encoded text files and converts legacy Windows-1252 encodings to UTF-8, so localized files will be loaded correctly. If properties/default.xml and/or properties/Layout.xml exist in the game data, they are loaded after LawnStrings.txt and can override any string value. Both files are optional; when absent, built-in English defaults are used.
Since default.xml takes priority over LawnStrings.txt, users can also create or edit their own properties/default.xml to add or override any string key, making it easy to fix version-specific display issues without modifying the engine.
Before building on PC, ensure you have the necessary dependencies installed:
- Build Tools:
CMake,Ninja, A C/C++ compiler (e.g.,gcc,clang,MSVC) supporting C++20 (Also need a standard library implementation likelibstdc++,libc++or MSVC STL that supports C++20) - Graphics:
OpenGL ES 2.0orOpenGL 2.1+(auto-detected at runtime via SDL2) - Audio:
libopenmpt,libogg,libvorbis,mpg123 - Image:
libpng,libjpeg-turbo - Windowing/Input:
SDL2
You can install the required dependencies using the following command:
sudo pacman -S --needed base-devel cmake libjpeg-turbo libogg libopenmpt libpng libvorbis mpg123 ninja sdl2-compatYou can install the required dependencies using the following command:
sudo apt install cmake ninja-build libogg-dev libjpeg-dev libopenmpt-dev libpng-dev libvorbis-dev libmpg123-dev libsdl2-devYou can install the required dependencies using the following command:
pacman -S --needed base-devel mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-libjpeg-turbo mingw-w64-ucrt-x86_64-libopenmpt mingw-w64-ucrt-x86_64-libogg mingw-w64-ucrt-x86_64-libpng mingw-w64-ucrt-x86_64-libvorbis mingw-w64-ucrt-x86_64-mpg123 mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-SDL2You can install the required dependencies using Homebrew with the following command:
brew install cmake dylibbundler jpeg-turbo libogg libopenmpt libpng libvorbis mpg123 ninja sdl2Run the following commands (assuming you have CMake and other dependencies installed) where the CMakeLists.txt file is located:
cmake -G Ninja -B buildcmake --build buildIt is recommended to use the Release build type for the best performance, as it usually implies compiler optimizations:
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=ReleaseFor advanced users, you can also specify CFLAGS and CXXFLAGS to enable architecture-specific optimizations (e.g. -march=native to fully utilize your CPU's instruction set):
cmake -G Ninja -B build -DCMAKE_C_FLAGS="-march=native" -DCMAKE_CXX_FLAGS="-march=native" -DCMAKE_BUILD_TYPE=ReleaseYou can customize the game features by adding options to the first cmake command:
| Option | Default | Description |
|---|---|---|
PVZ_DEBUG |
OFF( ON if CMAKE_BUILD_TYPE is Debug) |
Enable cheat keys, debug displays and other debug features. |
LIMBO_PAGE |
ON |
Enable access to the limbo page which contains hidden levels. |
DO_FIX_BUGS |
OFF |
Apply community fixes for "bugs" of official 1.2.0.1073 GOTY Edition.1 However, these "bugs" are usually considered "features" by many players. |
CONSOLE |
OFF( ON if CMAKE_BUILD_TYPE is Debug) |
Show a console window (Windows only). |
BUILD_STATIC |
OFF |
Link statically to create a standalone executable (Windows with MinGW-based toolchains only). Use a vcpkg -static triplet for MSVC instead. |
Example: Manually enable PVZ_DEBUG in Release build so that you can use cheat keys while having optimized performance:
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release -DPVZ_DEBUG=ONIf running these commands does not create a successful build please create an issue and detail your problem.
PvZ-Portable uses two distinct types of save data:
-
Global User Data (
users.dat,user1.dat, etc.):- Stores profile info, adventure progress, coins, Zen Garden, etc.
- Fully compatible with the original PC game format.
- Already portable by design (uses explicit serialization).
-
Mid-Level Save States (
game1_0.v4, etc. legacygame1_0.dat, etc.):- Stores the exact state of a level when "Save and Exit" is used (zombies, projectiles, plants, etc.).
- The game now writes only
*.v4files by default:*.v4files: Portable format. Sharing these files to transfer progress between different platforms is fully supported.*.datfiles: Legacy dump from old versions. Contains raw memory dumps. Do not share this file across platforms as it will cause crashes due to architecture differences.
- When loading, the game prefers
.v4;*.datis fallback-only for migration compatibility. - If a save is loaded from legacy
*.dat, the game automatically re-saves it to*.v4and removes the legacy*.datafter successful migration.
The original game's mid-level save format (.dat) effectively dumps raw memory structures to disk. This is fast but fragile, as it relies on specific memory layouts that break across:
- Different CPU Architectures: x86, ARM, RISC-V, LoongArch (alignment).
- 32-bit vs 64-bit builds: Pointer sizes and struct layouts differ.
- Different Compilers: MSVC vs GCC/Clang (padding, enum size, struct packing).
As a result, legacy saves are generally not guaranteed to load across those variants. To solve this, Format v4 serializes game objects field-by-field using Type-Length-Value (TLV) tags. This ensures that a level saved on a x86_64 Linux machine can be loaded on a LoongArch or ARM (Switch) machine without crashing.
- Add new fields as new TLV IDs; do not reuse IDs.
- Keep defaults for missing fields when loading older saves.
- Avoid raw struct dumps for data that may change layout; prefer explicit per-field sync with fixed-width primitives.
There's a Python script scripts/pvzp-v4-converter.py to inspect and modify .v4 save files.
Features:
- Info: View basic save stats (Wave, Sun, Zombies, etc.).
- Export to YAML: Convert binary
.v4files to human-readable YAML to view/edit gameplay-relevant data. - Import from YAML: Convert modified YAML back to
.v4format with correct checksums.
Usage:
# Modify these paths as needed
# View basic info
python scripts/pvzp-v4-converter.py info ~/.local/io.github.wszqkzqk/PvZPortable/userdata/game1_13.v4
# Export to YAML for editing
python scripts/pvzp-v4-converter.py export ~/.local/io.github.wszqkzqk/PvZPortable/userdata/game1_13.v4 level.yaml
# Import back to savegame
# **BACKUP** your original .v4 file fist!
mv ~/.local/io.github.wszqkzqk/PvZPortable/userdata/game1_13.v4 ~/.local/io.github.wszqkzqk/PvZPortable/userdata/game1_13.v4.bak
python scripts/pvzp-v4-converter.py import level.yaml ~/.local/io.github.wszqkzqk/PvZPortable/userdata/game1_13.v4When contributing please follow the following guides:
SexyAppFramework coding philosophy
The framework differs from many other APIs in that some class properties are not wrapped in accessor methods, but rather are made to be accessed directly through public member data. The window caption of your application, for example, is set by assigning a value to the std::string mTitle in the application object before the application’s window is created. We felt that in many cases this reduced the code required to implement a class. Also of note is the prefix notation used on variables: “m” denotes a class member, “the” denotes a parameter passed to a method or function, and “a” denotes a local variable.
Copyright (C) 2026 Zhou Qiankang wszqkzqk@qq.com
This project is licensed under the terms of the GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later).
- The repository includes complete license texts at the root:
LICENSE— LGPL-3.0 textCOPYING— GPL-3.0 text, referenced by LGPL-3.0
- The code is provided "as is", WITHOUT WARRANTY of any kind.
- The original game IP (Plants vs. Zombies) belongs to PopCap/EA. This license applies only to the code implementation in this repository.
- This project does NOT include any copyrighted assets from the original game.
The SexyAppFramework directory may contain code originally based on the PopCap Games Framework. This code is subject to the permissive PopCap Games Framework License. To the extent that original code remains, the following acknowledgment applies:
"This product includes portions of the PopCap Games Framework, © 2005-2009 PopCap Games, Inc. All rights reserved. (http://popcapframework.sourceforge.net/)."
Note that this code has been heavily refactored, optimized and modernized by the community over time under the LGPL-3.0-or-later license.
- @Headshotnoby: For almost fully implementing the 64-bit support and the initial fixed-function OpenGL backend.
- @Patoke: For the incredible initial reimplementation of PvZ GOTY.
- @rspforhp: For the 0.9.9 version's work.
- @ruslan831: For archiving the 0.9.9 version's re-implementation.
- PopCap Games: For creating the amazing game and releasing their framework to the public with a permissive license.
- The SDL Team: For the amazing cross-platform development library that powers this port.
- The OpenMPT Team: For libopenmpt, enabling high-quality MO3 music playback.
- All the contributors who have worked or are actively working in this amazing project.
Footnotes
-
Current
DO_FIX_BUGSincludes the following fixes:- Fix bungee zombie duplicate sun/item drop in I, Zombie mode.
- Make mind-controlled Gargantuars smash enemy zombies instead of plants.
- Make mind-controlled Gargantuars throw mind-controlled Imps (with scale, health, and direction fixes).
- Make mind-controlled Gargantuars can smash vases in Scary Potter mode.
- Make mind-controlled Pea/Gatling Head zombies shoot forward instead of backward.
- Make mind-controlled Jalapeno/Squash zombies damage enemy zombies instead of plants.
- Coordinate fixes for mind-controlled Squash zombies tracking and smashing enemy zombies.
- Make mind-controlled Jalapeno zombies correctly clear Dr. Zomboss' skills (Iceball/Fireball) and ladder logic.
- Sync Dancer Zombie animations (fixes "Maid" displacement bug).
- Fix visual glitch of Ladder Zombie's arm recovery.
- Fix Dr. Zomboss' attack (RV, Fireball/Iceball) and summon range coverage for 6-lane (Pool) levels.