# Architecture and Technical Documentation: GMoD-TeachGMAD

## 1. Overview

GMoD-TeachGMAD is a native Win32 application written in C++20 utilizing SDL2 for batch extraction, conversion, and installation of Garry's Mod addons from compressed workshop archives (.bin) and Source Engine packages (.gma).

### Design Principles
- Asynchronous Multithreaded Model: Heavy decompression workloads execute on a secondary worker thread without stalling the primary SDL render loop or the operating system message queue.
- RAII Resource Management: Deterministic release of Win32 handles, COM interfaces, and SDL graphical resources.
- Strict Modularity: Complete decoupling between extraction logic, platform abstraction, user interface, and configuration persistence.

---

## 2. Core Module (`src/core`)

### 2.1 `AppConfig` (`Config.hpp`)
Data structure storing configured directory paths.

```cpp
struct AppConfig {
    std::filesystem::path gmadLsDir; // Source directory (downloads / workshop cache)
    std::filesystem::path gmodDir;   // Root Garry's Mod directory
    bool IsValid() const;
};
```

- `IsValid()`: Verifies that both path members are non-empty, exist on the filesystem, and point to valid directories.

### 2.2 `ConfigManager` (`Config.hpp` / `Config.cpp`)
Static manager responsible for INI configuration persistence.

- `Load(const std::filesystem::path& iniPath, AppConfig& outConfig) -> bool`: Reads and parses `config.ini`, ignoring comments (`#`, `;`) and trimming whitespace. Returns true if the resulting configuration is valid.
- `Save(const std::filesystem::path& iniPath, const AppConfig& config) -> bool`: Serializes the configuration to disk.
- `Reset(const std::filesystem::path& iniPath) -> bool`: Deletes the `config.ini` file from disk.
- `GetDefaultConfigPath() -> std::filesystem::path`: Returns the absolute path of `config.ini` adjacent to the running executable.

---

## 3. Platform Module (`src/platform`)

Encapsulates low-level Windows API operations and platform utilities.

### 3.1 `PathUtils` (`PathUtils.hpp` / `PathUtils.cpp`)
Path resolution and string manipulation utilities.

- `GetExecutablePath() -> std::filesystem::path`: Retrieves the absolute path of the current binary via `GetModuleFileNameW`.
- `GetExecutableDirectory() -> std::filesystem::path`: Retrieves the parent directory containing the binary.
- `GetGmadExecutablePath() -> std::filesystem::path`: Returns the expected path for `gmad.exe`.
- `Get7zExecutablePath() -> std::filesystem::path`: Returns the expected path for `7za.exe`.
- `Trim(const std::string& s) -> std::string`: Removes leading and trailing whitespace, tabs, and newline characters.
- `ToLower(std::string s) -> std::string`: Converts an ASCII string to lowercase.
- `FindInnerPayload(const std::filesystem::path& directory) -> std::filesystem::path`: Iterates over a temporary directory to locate the extensionless inner payload extracted by 7-Zip.
- `CollectFilesByExtension(const std::filesystem::path& root, const std::string& targetExtension, bool recursive) -> std::vector<std::filesystem::path>`: Scans and aggregates file paths matching the specified file extension.

### 3.2 `Process` (`Process.hpp` / `Process.cpp`)
Abstraction for child process creation and synchronization via Win32 API.

- `struct ProcessResult`: Contains `bool executed` and `DWORD exitCode`.
- `Execute(const std::filesystem::path& executable, const std::wstring& arguments, bool hidden, DWORD timeoutMs) -> ProcessResult`: Invokes `CreateProcessW` with escaped arguments. Manages process and thread handles using RAII (`ScopedHandle`).
- `ExecuteCommandLine(const std::wstring& commandLine, bool hidden, DWORD timeoutMs) -> ProcessResult`: Executes a raw command line with optional flags such as `CREATE_NO_WINDOW`.
- `OpenBrowserURL(const std::string& url) -> void`: Launches the system default browser via `ShellExecuteA`.
- `RestartSelf() -> void`: Spawns a new instance of the current binary and terminates the active process via `ExitProcess(0)`.

### 3.3 `Dialogs` (`Dialogs.hpp` / `Dialogs.cpp`)
Wrappers for native Windows system dialogs.

- `OpenFolderDialog(const std::wstring& title, HWND parent) -> std::optional<std::filesystem::path>`: Displays the native Windows folder picker using the COM `IFileOpenDialog` interface with `FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM`. Handles `CoInitializeEx`/`CoUninitialize` automatically via RAII.
- `ShowInfoDialog(const std::string& title, const std::string& message, HWND parent) -> void`: Displays an informational `MessageBoxA`.
- `ShowErrorDialog(const std::string& title, const std::string& message, HWND parent) -> void`: Displays an error `MessageBoxA`.

---

## 4. Extractor Module (`src/extractor`)

Implements the decompression, conversion, and redistribution logic for Garry's Mod addons.

### 4.1 `PipelineStats` (`GmadExtractor.hpp`)
Metrics tracked across an extraction session:
- `totalBinsFound`: Count of discovered `.bin` files.
- `totalGmasFound`: Count of discovered `.gma` files.
- `binsProcessed`: Successfully decompressed `.bin` archives.
- `gmasProcessed`: Successfully extracted `.gma` packages.
- `errors`: Number of failed operations.

### 4.2 `GmadExtractor` (`GmadExtractor.hpp` / `GmadExtractor.cpp`)
Core extraction engine.

- `ExtractWith7Zip(const fs::path& archive, const fs::path& outDir, const fs::path& custom7zExe) -> bool`: Executes `7za.exe x "<archive>" -o"<outDir>" -y` in hidden mode. Verifies that the destination directory contains output files regardless of non-zero minor return codes from 7-Zip.
- `ProcessBinFile(const fs::path& binPath, const fs::path& gmadExe, const fs::path& gmodDir, const fs::path& custom7zExe) -> bool`:
  1. Creates a temporary subdirectory `<binPath_stem>_extracted`.
  2. Decompresses the `.bin` using 7-Zip.
  3. Locates the extensionless inner payload (`FindInnerPayload`).
  4. Renames the payload to `.gma`.
  5. Passes the renamed package to `ProcessGmaFile`.
  6. Deletes the temporary subdirectory.
- `ProcessGmaFile(const fs::path& gmaPath, const fs::path& gmadExe, const fs::path& gmodDir) -> bool`:
  1. Invokes `gmad.exe extract -file "<gmaPath>"`.
  2. Locates the extracted output folder produced by GMAD (`<gmaPath_parent>/<gmaPath_stem>`).
  3. Forwards the folder to `ProcessExtractedFolder`.
- `ProcessExtractedFolder(const fs::path& extractedDir, const fs::path& gmodDir) -> bool`:
  - If the directory contains a `maps/` subdirectory: Recursively moves the contents of `maps/` to `<gmodDir>/maps/`, removes the residual `maps` folder, and moves the remaining addon assets to `<gmodDir>/addons/<addon_name>/`.
  - If no `maps/` folder exists: Moves the entire directory directly to `<gmodDir>/addons/<addon_name>/`.
- `MoveItem(const fs::path& src, const fs::path& dst) -> bool`: Attempts `std::filesystem::rename`. On failure (cross-volume operations or locks), falls back to recursive `std::filesystem::copy` with overwrite followed by source removal.
- `MoveDirectoryContents(const fs::path& srcDir, const fs::path& dstDir, const std::string& skipName) -> bool`: Iterates through entries in a directory and moves them to the target path, bypassing entries matching `skipName`.
- `CleanSourceDirectory(const fs::path& sourceDir) -> bool`: Recursively removes all files and directories inside the source directory.
- `RunInstallationPipeline(const Core::AppConfig& config, bool cleanAfterwards, ProgressCallback progressCb) -> PipelineStats`: Primary pipeline orchestrator. Sequentially processes all `.bin` archives, rescans for generated `.gma` files, extracts all `.gma` packages, performs optional cleanup, and continuously emits progress callbacks `(float ratio, string status)`.

---

## 5. UI Module (`src/ui`)

### 5.1 `UIConstants` (`UIConstants.hpp`)
Layout and styling constants:
- Window dimensions: `800 x 300` px.
- Color definitions: `CLR_GMOD_BLUE (0, 130, 254)`, `CLR_WHITE (255, 255, 255)`, `CLR_DISABLED (180, 180, 180)`.
- Asset paths and URLs.

### 5.2 `Renderer` (`Renderer.hpp` / `Renderer.cpp`)
Rendering helper functions for SDL2 and SDL_ttf.
- `DrawText(SDL_Renderer*, TTF_Font*, const std::string& text, int x, int y, SDL_Color color)`: Renders UTF-8 text with alpha blending.
- `DrawCenteredText(SDL_Renderer*, TTF_Font*, const std::string& text, const SDL_Rect& bounds, SDL_Color color)`: Computes the bounding box of the string and centers it within the target rectangle.
- `GetTextSize(TTF_Font*, const std::string& text, int& outW, int& outH)`: Measures pixel dimensions of rendered text.

### 5.3 `Button` (`Button.hpp` / `Button.cpp`)
Interactive button component:
- `Contains(int mouseX, int mouseY) const -> bool`: Point-in-rectangle collision test.
- `UpdateHover(int mouseX, int mouseY) -> void`: Updates `m_hovered` state based on mouse coordinates.
- `Draw(SDL_Renderer*, TTF_Font*) const -> void`:
  - Normal state: Blue fill, white border, white label.
  - Hover state: Solid white fill, blue label.
  - Disabled state: Dimmed gray border and label.

### 5.4 `ProgressBar` (`ProgressBar.hpp` / `ProgressBar.cpp`)
Progress bar component with dynamic status label:
- `SetProgress(float value)`: Clamps the value between `0.0f` and `1.0f`.
- `SetStatusText(const std::string& text)`: Updates the descriptive status text.
- `Draw(SDL_Renderer*, TTF_Font*) const -> void`: Renders the outer container, proportional fill rectangle, and status message below.

---

## 6. Application Module (`src/app`)

### 6.1 `Application` (`Application.hpp` / `Application.cpp`)
Main application controller.

- `Initialize() -> bool`: Initializes SDL2 subsystems (Video, Events, TTF, PNG Image), creates the main window (800x300), initializes hardware-accelerated renderer with VSYNC, loads assets, and validates or triggers initial configuration dialogs.
- `Run() -> int`: Main application loop capped at ~60 FPS (`SDL_Delay(16)`).
- `ProcessEvents() -> void`: Handles window closing (`SDL_QUIT`) and mouse clicks (`SDL_MOUSEBUTTONDOWN`).
- `Update() -> void`: Polls mouse position, updates button hover states, synchronizes atomic progress (`m_progress`), and disables controls while `m_isProcessing` is active.
- `Render() -> void`: Clears the frame buffer, renders buttons, title banner, and progress bar, and presents the backbuffer via `SDL_RenderPresent`.
- `StartPipelineAsync(bool cleanAfterwards) -> void`: Spawns the extraction task on a background worker thread (`std::thread`), preventing UI freezes during massive batch workloads.
- `Shutdown() -> void`: Joins pending worker threads, destroys textures, fonts, renderer, and window, and cleans up SDL subsystems.

---

## 7. Entry Point (`src/main.cpp`)

Instantiates `GModTeach::App::Application` and executes `app.Run()`, forwarding the exit code to the operating system.
