# Faded Local Mod Installer

Faded Local Mod Installer is a simple Windows desktop app for downloading local, non-Workshop Project Zomboid mods from a public `manifest.json` file and installing them into the current user's local mods folder.

Default install path:

```text
%USERPROFILE%\Zomboid\mods
```

The app is intentionally simple for the MVP:

1. The app loads a public `manifest.json` URL.
2. The app displays each mod's name, version, and description.
3. The user selects one or more mods.
4. The app downloads ZIP files from each manifest entry's `download_url`.
5. The app extracts and installs them to `%USERPROFILE%\Zomboid\mods`.

Normal users do not need API keys, login, OAuth, or cloud setup.

## Valid Mod ZIP Structure

Each ZIP should contain one proper Project Zomboid mod folder. Older/simple mods can place `mod.info` directly inside the mod folder:

```text
YourModFolder/
  mod.info
  media/
  poster.png
```

The final installed structure should look like this:

```text
C:\Users\<USERNAME>\Zomboid\mods\YourModFolder\mod.info
C:\Users\<USERNAME>\Zomboid\mods\YourModFolder\media\...
```

Build-specific Project Zomboid 42 mods are also supported:

```text
YourModFolder/
  42/
    mod.info
    media/
  common/
```

Those install as:

```text
C:\Users\<USERNAME>\Zomboid\mods\YourModFolder\42\mod.info
```

The installer is designed to avoid broken nested installs like:

```text
mods\YourModFolder\YourModFolder\mod.info
```

## Tech Stack

This project uses Python, PySide6, and PyInstaller.

That stack was chosen because it is practical for a small Windows desktop utility: the source stays readable, the UI is native enough for normal players, and PyInstaller can produce a single `.exe` without requiring users to install Python.

## Project Structure

```text
app/
  app_config.py       App settings and default paths
  manifest_client.py  Public manifest loading and ZIP downloading
  installer.py        Safe zip extraction, validation, backup, and install logic
  logger.py           Log file setup
  ui.py               PySide6 desktop app
  version.py          App version
examples/
  mod_manifest.example.json
installer/
  FadedLocalModInstaller.iss
scripts/
  build_exe.ps1
  package_zip.ps1
tests/
  test_installer.py
  test_manifest_client.py
main.py
requirements.txt
README.md
```

## How Players Use It

1. Open the app.
2. Go to **Settings**.
3. Paste the public `manifest.json` URL into **Manifest URL**.
4. Confirm the install path. The default is usually correct:

   ```text
   %USERPROFILE%\Zomboid\mods
   ```

5. Go back to **Player Install**.
6. Click **Refresh Mod List**.
7. Select one or more mods.
8. Click **Download & Install**.

If a mod is already installed, the app offers:

- Backup Then Replace
- Replace Existing Mod
- Skip Existing Mod

Backups are stored here:

```text
%APPDATA%\FadedLocalModInstaller\backups
```

Logs are stored here:

```text
%APPDATA%\FadedLocalModInstaller\logs\installer.log
```

## manifest.json Setup

Host a public `manifest.json` file anywhere that can be reached by a normal browser. GitHub Pages, a static web host, your own website, or a public object storage URL are all fine.

Example:

```json
{
  "manifest_version": 1,
  "app_name": "Faded Local Mod Installer",
  "mods": [
    {
      "id": "your-first-mod-id",
      "name": "Your First Mod Name",
      "version": "1.0.0",
      "description": "Short plain-language description of this mod.",
      "download_url": "https://github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/releases/download/your-first-mod-id-v1.0.0/YOUR_FIRST_MOD_ZIP.zip",
      "zip_name": "YOUR_FIRST_MOD_ZIP.zip"
    },
    {
      "id": "your-second-mod-id",
      "name": "Your Second Mod Name",
      "version": "1.0.0",
      "description": "Short plain-language description of this mod.",
      "download_url": "https://github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/releases/download/your-second-mod-id-v1.0.0/YOUR_SECOND_MOD_ZIP.zip",
      "zip_name": "YOUR_SECOND_MOD_ZIP.zip"
    }
  ]
}
```

Rules:

- `manifest_version` must be `1`.
- `app_name` should be `Faded Local Mod Installer`.
- `mods` must be an array.
- Each mod must have a unique `id`.
- Each mod must have a readable `name`.
- Each mod must have a `version`.
- Each mod must have a short `description`.
- Each mod must have a public direct `download_url`.
- Each mod must have a `zip_name` ending in `.zip`.
- Each `download_url` should download a ZIP file without requiring login.
- Users should not need API keys, login, OAuth, or cloud setup.
- Large mods can use `download_parts` instead of one `download_url`.
- Each `download_parts` entry must include a public direct `download_url` and `.zip` `zip_name`.

An example file is included at:

```text
examples\mod_manifest.example.json
```

## GitHub Mod Host Repo

This project includes a starter repo template here:

```text
github-mod-host-repo\
```

Copy that folder into its own GitHub repository.

Recommended hosted layout:

```text
manifest.json
releases/
  README.md
```

For small ZIP files, you can put ZIPs in `releases/` and use raw GitHub URLs in `download_url`.

For large ZIP files, use GitHub Release assets instead of committing ZIPs into the repository.

GitHub blocks normal repository files larger than 100 MiB. GitHub Releases allow release assets under 2 GiB. A large mod ZIP should use a release asset URL like:

```text
https://github.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/releases/download/MOD_ID-vVERSION/YOUR_MOD_ZIP.zip
```

The app does not require the GitHub API. It only needs a public raw `manifest.json` URL and public ZIP download URLs.

After pushing the GitHub mod host repo, use this kind of URL in the installer's **Manifest URL** setting:

```text
https://raw.githubusercontent.com/YOUR_GITHUB_USERNAME/YOUR_REPO_NAME/main/manifest.json
```

If you accidentally use a GitHub `blob` URL for a ZIP, the app converts it to a raw GitHub URL internally.

## Server Owner Instructions

The app includes a **Server Owner Instructions** tab. It does not automatically install to servers yet.

Windows dedicated server path:

```text
C:\Users\<SERVER_USER>\Zomboid\mods
```

Linux dedicated server path:

```text
~/Zomboid/mods
```

Hosted servers usually require the host's File Manager or FTP/SFTP. Look for a `Zomboid`, server profile, or `mods` folder and upload the mod folder so `mod.info` is directly inside the mod folder:

```text
Zomboid/mods/ExampleMod/mod.info
Zomboid/mods/ExampleMod/media/...
```

Local non-Workshop mods must exist on the server and on every client. Server owners may still need to add the mod ID to the server config. The mod ID is usually inside `mod.info`. This tool does not replace proper server config setup.

## Run Locally

From this project folder:

```powershell
python -m venv .venv
.\.venv\Scripts\activate
pip install -r requirements.txt
python main.py
```

## Run Tests

```powershell
python -m unittest discover -s tests
```

The tests cover manifest parsing, safe zip extraction, `mod.info` validation, duplicate nested folder avoidance, existing mod backup, and default install path behavior.

## Build The Windows EXE

Manual build:

```powershell
python -m venv .venv
.\.venv\Scripts\activate
pip install -r requirements.txt
pyinstaller --onefile --windowed --name "Faded Local Mod Installer" main.py
```

Or run:

```powershell
.\scripts\build_exe.ps1
```

If PowerShell blocks scripts on your machine, use:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\build_exe.ps1
```

The built app will be:

```text
dist\Faded Local Mod Installer.exe
```

## Create A Downloadable Zip Package

After building the `.exe`:

```powershell
.\scripts\package_zip.ps1
```

If PowerShell blocks scripts on your machine, use:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\package_zip.ps1
```

The package will be created in:

```text
release\FadedLocalModInstaller-0.1.0-windows.zip
```

Include:

- `Faded Local Mod Installer.exe`
- `README.md`

Do not include:

- `.venv`
- `build`
- private `config.json`
- `.env`
- secrets
- tokens
- logs
- cache folders

## Optional Windows Installer

Install Inno Setup, build the `.exe`, then compile:

```text
installer\FadedLocalModInstaller.iss
```

That creates an installer in:

```text
release\
```

## Updating Mods Later

To update mod files for players:

1. Create a new valid zip for the mod.
2. Upload the zip to your hosting location.
3. Update the mod's `version` in `manifest.json`.
4. Update the `download_url` or `zip_name` in `manifest.json` if either changed.
5. Keep the zip structure valid: one mod folder with `mod.info` directly inside.
6. Players click **Refresh Mod List** and install again.
7. If the mod already exists, players can backup and replace it.

To update the app:

1. Edit the source.
2. Update `APP_VERSION` in `app/version.py`.
3. Update the version in `scripts/package_zip.ps1` and `installer/FadedLocalModInstaller.iss`.
4. Run tests.
5. Build a new `.exe`.
6. Package a new release zip or installer.

## Security Notes

- The app never runs downloaded files.
- The app only downloads, validates, extracts, and copies mod folders.
- Zip extraction blocks path traversal attempts.
- Existing mods are not overwritten without a user choice.
- Backups use timestamped folders.
- No credentials are required for normal users.
- The app does not collect user data.
- There is no telemetry.

## Troubleshooting

If the mod list does not load:

- Confirm the Manifest URL opens in a browser.
- Confirm the manifest is valid JSON.
- Confirm `manifest_version` is `1`.
- Confirm each manifest entry has `id`, `name`, `version`, `description`, `download_url`, and `zip_name`.
- Confirm each `download_url` is public and does not require login.
- Check the log file.

If a download fails:

- Confirm the ZIP file still exists at the `download_url`.
- Confirm the link downloads the ZIP directly.
- If using GitHub Release assets, confirm the release is public.
- If using a ZIP committed into `releases/`, confirm it is under GitHub's normal repository file limit.
- If the app says the download is a web page instead of a ZIP, GitHub is returning a web page, permission page, or missing-file page instead of the ZIP.
- Try downloading the file manually from the browser.

If install fails:

- Confirm the zip contains exactly one `mod.info`.
- Confirm `mod.info` is directly inside the real mod folder.
- Confirm Project Zomboid is not locking files.
- Confirm the app has permission to write to the selected install path.

If the app says multiple mod folders were found:

- The zip likely contains more than one `mod.info`.
- Split those into separate mod zip files.

If the mods folder does not exist:

- Let the app create it, or create it manually at:

  ```text
  %USERPROFILE%\Zomboid\mods
  ```
