Progress on documentation

This commit is contained in:
2026-02-20 14:00:40 -08:00
parent d59cf82ab3
commit f421a8e436
413 changed files with 1681 additions and 109 deletions

View File

@@ -27,7 +27,8 @@ Generate documentation following this exact structure:
### 1. Header ### 1. Header
Format: `# Room ##: [Room Name]` Format: `# Room ##: [Room Name]`
**Filename**: `kq4-##-human-readable-name.md` **Directory**: `rooms/kq4-##-human-readable-name/`
**Filename inside directory**: `kq4-##-human-readable-name.md`
### 2. High-Level Summary ### 2. High-Level Summary
A paragraph describing the room's purpose, key features, NPCs, and gameplay significance. A paragraph describing the room's purpose, key features, NPCs, and gameplay significance.
@@ -187,21 +188,29 @@ Follow the format exactly:
5. Scripts table 5. Scripts table
6. Technical notes with state variables 6. Technical notes with state variables
### Step 7: Save File ### Step 7: Generate Visual PNG
Save to: `rooms/kq4-###-human-readable-name.md` After saving the documentation, generate the room visual:
1. Run: `./sci_pic_render <room_number> "<path_to_game>" rooms/kq4-###-human-readable-name/`
2. This creates three PNG files:
- `pic_###_visual.png` - Background visual
- `pic_###_control.png` - Control (walkable areas)
- `pic_###_priority.png` - Priority (z-ordering)
3. Move the markdown file into the room directory: `mv rooms/kq4-###-human-readable-name.md rooms/kq4-###-human-readable-name/`
### Step 8: Update README.md ### Step 8: Update README.md
After saving the room documentation: After saving the room documentation:
1. Read the current README.md to find the Progress section 1. Read the current README.md to find the Progress section
2. Find the row for this room number 2. Find the row for this room number
3. Update the Status to `[DONE](./rooms/kq4-###-human-readable-name.md)` 3. Update the Status to include embedded PNG and link:
- With PNG: `![pic_###_visual.png](./rooms/kq4-###-human-readable-name/pic_###_visual.png) [DONE](./rooms/kq4-###-human-readable-name/kq4-###-human-readable-name.md)`
- Without PNG (if render fails): `[DONE](./rooms/kq4-###-human-readable-name/kq4-###-human-readable-name.md)`
4. Fill in the Room Description with a human-readable name (e.g., "Beach", "Fountain Pool", "Cave Entrance") 4. Fill in the Room Description with a human-readable name (e.g., "Beach", "Fountain Pool", "Cave Entrance")
## Example Reference ## Example Reference
See `rooms/055-seven-dwarfs-diamond-mine.md` for a complete example following this format. See `rooms/kq4-055-seven-dwarfs-diamond-mine/kq4-055-seven-dwarfs-diamond-mine.md` for a complete example following this format.
## Command Reference ## Command Reference
@@ -322,5 +331,7 @@ Document all applied regions and check each for additional interactions.
- Include full message text in quotes - Include full message text in quotes
- Organize interactions logically by type - Organize interactions logically by type
- Be thorough but concise in descriptions - Be thorough but concise in descriptions
- Filename must be: `kq4-##-human-readable-name.md` (e.g., `kq4-024-waterfall-and-pool.md`) - Create directory: `rooms/kq4-##-human-readable-name/`
- After completing the documentation, update README.md with the room description and link - Save markdown as: `rooms/kq4-##-human-readable-name/kq4-##-human-readable-name.md`
- Generate PNGs using sci_pic_render into the room directory
- After completing the documentation, update README.md with the room description, embedded PNG thumbnail, and link

240
README.md
View File

@@ -6,12 +6,15 @@ A comprehensive documentation of all game interactions extracted from the decomp
``` ```
/ /
├── rooms/ # Room-specific interaction documentation ├── rooms/ # Room-specific documentation (one directory per room)
│ ├── kq4-001.md # Beach (starting room) │ ├── kq4-001-beach/ # Beach (starting room)
│ ├── kq4-002.md # Beach continuation │ ├── 001-beach.md # Room documentation
│ ├── kq4-007.md # Fisherman's shack exterior │ ├── pic_001_visual.png # Background visual
│ ├── kq4-042.md # Fisherman's cottage interior │ ├── pic_001_control.png # Control (walkable areas)
│ └── ... # One file per room │ └── pic_001_priority.png # Priority (z-ordering)
│ ├── kq4-002-meadow/ # Meadow (with Satyr/Pan)
│ ├── kq4-010-forest-path/ # Forest Path
│ └── ... # One directory per room
├── KQ4_v1.006.004_int0.000.502_SRC_(6)/ ├── KQ4_v1.006.004_int0.000.502_SRC_(6)/
│ └── src/ # Decompiled SCI scripts │ └── src/ # Decompiled SCI scripts
│ ├── Main.sc # Game engine & global handlers │ ├── Main.sc # Game engine & global handlers
@@ -218,6 +221,39 @@ When adapting to a KQ5-style interface (walk, look, hand, talk, inventory):
Text messages referenced as `Print scriptNum messageNum` will be extracted from the game's RESOURCE files and added to each room's documentation. Text messages referenced as `Print scriptNum messageNum` will be extracted from the game's RESOURCE files and added to each room's documentation.
## Generating Room Visuals
Each room directory contains rendered PNG images of the game's PIC resources:
### Using sci_pic_render
```bash
./sci_pic_render <room_number> "<path_to_game>" <output_directory>
```
**Example:**
```bash
./sci_pic_render 1 "King's Quest IV - The Perils of Rosella (1988)/KQ4" rooms/kq4-001-beach
```
This generates three PNG files in the room directory:
- `pic_###_visual.png` - The background visual
- `pic_###_control.png` - Control (walkable areas)
- `pic_###_priority.png` - Priority (z-ordering)
### Directory Structure
```
rooms/
├── kq4-001-beach/
│ ├── 001-beach.md # Room documentation
│ ├── pic_001_visual.png # Background
│ ├── pic_001_control.png # Walkable areas
│ └── pic_001_priority.png # Z-ordering
└── kq4-010-forest-path/
└── ...
```
## Contributing ## Contributing
When adding room documentation: When adding room documentation:
@@ -233,105 +269,105 @@ When adding room documentation:
| Room Number | Room Description | Status | | Room Number | Room Description | Status |
|-------------|------------------|--------| |-------------|------------------|--------|
| 001 | Beach | [DONE](./rooms/kq4-001-beach.md) | | 001 | Beach | ![pic_001_visual.png](./rooms/kq4-001-beach/pic_001_visual.png) [DONE](./rooms/kq4-001-beach/001-beach.md) |
| 002 | Meadow (with Satyr/Pan) | [DONE](./rooms/kq4-002-meadow.md) | | 002 | Meadow (with Satyr/Pan) | ![pic_002_visual.png](./rooms/kq4-002-meadow/pic_002_visual.png) [DONE](./rooms/kq4-002-meadow/kq4-002-meadow.md) |
| 003 | Fountain Pool | [DONE](./rooms/kq4-003-fountain-pool.md) | | 003 | Fountain Pool | ![pic_003_visual.png](./rooms/kq4-003-fountain-pool/pic_003_visual.png) [DONE](./rooms/kq4-003-fountain-pool/kq4-003-fountain-pool.md) |
| 004 | Ogre's Cottage Exterior | [DONE](./rooms/kq4-004-ogres-cottage.md) | | 004 | Ogre's Cottage Exterior | ![pic_004_visual.png](./rooms/kq4-004-ogres-cottage/pic_004_visual.png) [DONE](./rooms/kq4-004-ogres-cottage/kq4-004-ogres-cottage.md) |
| 005 | Forest Grove | [DONE](./rooms/kq4-005-forest-grove.md) | | 005 | Forest Grove | ![pic_005_visual.png](./rooms/kq4-005-forest-grove/pic_005_visual.png) [DONE](./rooms/kq4-005-forest-grove/kq4-005-forest-grove.md) |
| 006 | Cave Entrance | [DONE](./rooms/kq4-006-cave-entrance.md) | | 006 | Cave Entrance | ![pic_006_visual.png](./rooms/kq4-006-cave-entrance/pic_006_visual.png) [DONE](./rooms/kq4-006-cave-entrance/kq4-006-cave-entrance.md) |
| 007 | Fisherman's Shack Exterior | [DONE](./rooms/kq4-007-fishermans-shack.md) | | 007 | Fisherman's Shack Exterior | ![pic_007_visual.png](./rooms/kq4-007-fishermans-shack/pic_007_visual.png) [DONE](./rooms/kq4-007-fishermans-shack/kq4-007-fishermans-shack.md) |
| 008 | Back of Fisherman's Shack | [DONE](./rooms/kq4-008-back-of-fishermans-shack.md) | | 008 | Back of Fisherman's Shack | ![pic_008_visual.png](./rooms/kq4-008-back-of-fishermans-shack/pic_008_visual.png) [DONE](./rooms/kq4-008-back-of-fishermans-shack/kq4-008-back-of-fishermans-shack.md) |
| 009 | Shady Wooded Area | [DONE](./rooms/kq4-009-shady-wooded-area.md) | | 009 | Shady Wooded Area | ![pic_009_visual.png](./rooms/kq4-009-shady-wooded-area/pic_009_visual.png) [DONE](./rooms/kq4-009-shady-wooded-area/kq4-009-shady-wooded-area.md) |
| 010 | Forest Path | [DONE](./rooms/kq4-010-forest-path.md) | | 010 | Forest Path | ![pic_010_visual.png](./rooms/kq4-010-forest-path/pic_010_visual.png) [DONE](./rooms/kq4-010-forest-path/kq4-010-forest-path.md) |
| 011 | Enchanted Grove | [DONE](./rooms/kq4-011-enchanted-grove.md) | | 011 | Enchanted Grove | ![pic_011_visual.png](./rooms/kq4-011-enchanted-grove/pic_011_visual.png) [DONE](./rooms/kq4-011-enchanted-grove/kq4-011-enchanted-grove.md) |
| 012 | Haunted Forest | [DONE](./rooms/kq4-012-haunted-forest.md) | | 012 | Haunted Forest | ![pic_012_visual.png](./rooms/kq4-012-haunted-forest/pic_012_visual.png) [DONE](./rooms/kq4-012-haunted-forest/kq4-012-haunted-forest.md) |
| 013 | Beach | [DONE](./rooms/kq4-013-beach.md) | | 013 | Beach | ![pic_013_visual.png](./rooms/kq4-013-beach/pic_013_visual.png) [DONE](./rooms/kq4-013-beach/kq4-013-beach.md) |
| 014 | Green Meadow | [DONE](./rooms/kq4-014-green-meadow.md) | | 014 | Green Meadow | ![pic_014_visual.png](./rooms/kq4-014-green-meadow/pic_014_visual.png) [DONE](./rooms/kq4-014-green-meadow/kq4-014-green-meadow.md) |
| 015 | The Frog Pond | [DONE](./rooms/kq4-015-frog-pond.md) | | 015 | The Frog Pond | ![pic_015_visual.png](./rooms/kq4-015-frog-pond/pic_015_visual.png) [DONE](./rooms/kq4-015-frog-pond/kq4-015-frog-pond.md) |
| 016 | Graveyard | [DONE](./rooms/kq4-016-graveyard.md) | | 016 | Graveyard | ![pic_016_visual.png](./rooms/kq4-016-graveyard/pic_016_visual.png) [DONE](./rooms/kq4-016-graveyard/kq4-016-graveyard.md) |
| 017 | Spooky House Exterior | [DONE](./rooms/kq4-017-spooky-house-exterior.md) | | 017 | Spooky House Exterior | ![pic_017_visual.png](./rooms/kq4-017-spooky-house-exterior/pic_017_visual.png) [DONE](./rooms/kq4-017-spooky-house-exterior/kq4-017-spooky-house-exterior.md) |
| 018 | Cemetery | [DONE](./rooms/kq4-018-cemetery.md) | | 018 | Cemetery | ![pic_018_visual.png](./rooms/kq4-018-cemetery/pic_018_visual.png) [DONE](./rooms/kq4-018-cemetery/kq4-018-cemetery.md) |
| 019 | Coastal Cliffs | [DONE](./rooms/kq4-019-coastal-cliffs.md) | | 019 | Coastal Cliffs | ![pic_019_visual.png](./rooms/kq4-019-coastal-cliffs/pic_019_visual.png) [DONE](./rooms/kq4-019-coastal-cliffs/kq4-019-coastal-cliffs.md) |
| 020 | Meadow | [DONE](./rooms/kq4-020-meadow.md) | | 020 | Meadow | ![pic_020_visual.png](./rooms/kq4-020-meadow/pic_020_visual.png) [DONE](./rooms/kq4-020-meadow/kq4-020-meadow.md) |
| 021 | Bridge Over Stream | [DONE](./rooms/kq4-021-bridge-over-stream.md) | | 021 | Bridge Over Stream | ![pic_021_visual.png](./rooms/kq4-021-bridge-over-stream/pic_021_visual.png) [DONE](./rooms/kq4-021-bridge-over-stream/kq4-021-bridge-over-stream.md) |
| 022 | Gnome's Cottage | [DONE](./rooms/kq4-022-gnomes-cottage.md) | | 022 | Gnome's Cottage | ![pic_022_visual.png](./rooms/kq4-022-gnomes-cottage/pic_022_visual.png) [DONE](./rooms/kq4-022-gnomes-cottage/kq4-022-gnomes-cottage.md) |
| 023 | Forest Path with Cottage | [DONE](./rooms/kq4-023-forest-path-with-cottage.md) | | 023 | Forest Path with Cottage | ![pic_023_visual.png](./rooms/kq4-023-forest-path-with-cottage/pic_023_visual.png) [DONE](./rooms/kq4-023-forest-path-with-cottage/kq4-023-forest-path-with-cottage.md) |
| 024 | Waterfall and Pool | [DONE](./rooms/kq4-024-waterfall-and-pool.md) | | 024 | Waterfall and Pool | ![pic_024_visual.png](./rooms/kq4-024-waterfall-and-pool/pic_024_visual.png) [DONE](./rooms/kq4-024-waterfall-and-pool/024-waterfall-and-pool.md) |
| 025 | Beach at River Delta | [DONE](./rooms/kq4-025-beach-at-river-delta.md) | | 025 | Beach at River Delta | ![pic_025_visual.png](./rooms/kq4-025-beach-at-river-delta/pic_025_visual.png) [DONE](./rooms/kq4-025-beach-at-river-delta/kq4-025-beach-at-river-delta.md) |
| 026 | River Meadow | [DONE](./rooms/kq4-026-river-meadow.md) | | 026 | River Meadow | ![pic_026_visual.png](./rooms/kq4-026-river-meadow/pic_026_visual.png) [DONE](./rooms/kq4-026-river-meadow/kq4-026-river-meadow.md) |
| 027 | Forest Path | [DONE](./rooms/kq4-027-forest-path.md) | | 027 | Forest Path | ![pic_027_visual.png](./rooms/kq4-027-forest-path/pic_027_visual.png) [DONE](./rooms/kq4-027-forest-path/kq4-027-forest-path.md) |
| 028 | Mine Entrance | [DONE](./rooms/kq4-028-mine-entrance.md) | | 028 | Mine Entrance | ![pic_028_visual.png](./rooms/kq4-028-mine-entrance/pic_028_visual.png) [DONE](./rooms/kq4-028-mine-entrance/kq4-028-mine-entrance.md) |
| 029 | Dense Forest | [DONE](./rooms/kq4-029-dense-forest.md) | | 029 | Dense Forest | ![pic_029_visual.png](./rooms/kq4-029-dense-forest/pic_029_visual.png) [DONE](./rooms/kq4-029-dense-forest/kq4-029-dense-forest.md) |
| 030 | Mountain Pass | [DONE](./rooms/kq4-030-mountain-pass.md) | | 030 | Mountain Pass | ![pic_030_visual.png](./rooms/kq4-030-mountain-pass/pic_030_visual.png) [DONE](./rooms/kq4-030-mountain-pass/kq4-030-mountain-pass.md) |
| 031 | Open Ocean | [DONE](./rooms/kq4-031-open-ocean.md) | | 031 | Open Ocean | ![pic_031_visual.png](./rooms/kq4-031-open-ocean/pic_031_visual.png) [DONE](./rooms/kq4-031-open-ocean/kq4-031-open-ocean.md) |
| 032 | Ocean Near Island | [DONE](./rooms/kq4-032-ocean-near-island.md) | | 032 | Ocean Near Island | ![pic_032_visual.png](./rooms/kq4-032-ocean-near-island/pic_032_visual.png) [DONE](./rooms/kq4-032-ocean-near-island/kq4-032-ocean-near-island.md) |
| 033 | Enchanted Island Beach | [DONE](./rooms/kq4-033-enchanted-island-beach.md) | | 033 | Enchanted Island Beach | ![pic_033_visual.png](./rooms/kq4-033-enchanted-island-beach/pic_033_visual.png) [DONE](./rooms/kq4-033-enchanted-island-beach/kq4-033-enchanted-island-beach.md) |
| 034 | Island Beach | [DONE](./rooms/kq4-034-island-beach.md) | | 034 | Island Beach | ![pic_034_visual.png](./rooms/kq4-034-island-beach/pic_034_visual.png) [DONE](./rooms/kq4-034-island-beach/kq4-034-island-beach.md) |
| 035 | Island Beach | [DONE](./rooms/kq4-035-island-beach.md) | | 035 | Island Beach | ![pic_035_visual.png](./rooms/kq4-035-island-beach/pic_035_visual.png) [DONE](./rooms/kq4-035-island-beach/kq4-035-island-beach.md) |
| 036 | Island Garden Pond | [DONE](./rooms/kq4-036-island-garden-pond.md) | | 036 | Island Garden Pond | ![pic_036_visual.png](./rooms/kq4-036-island-garden-pond/pic_036_visual.png) [DONE](./rooms/kq4-036-island-garden-pond/kq4-036-island-garden-pond.md) |
| 037 | Fairy Island | [DONE](./rooms/kq4-037-fairy-island.md) | | 037 | Fairy Island | ![pic_037_visual.png](./rooms/kq4-037-fairy-island/pic_037_visual.png) [DONE](./rooms/kq4-037-fairy-island/kq4-037-fairy-island.md) |
| 038 | Island Garden | [DONE](./rooms/kq4-038-island-garden.md) | | 038 | Island Garden | ![pic_038_visual.png](./rooms/kq4-038-island-garden/pic_038_visual.png) [DONE](./rooms/kq4-038-island-garden/kq4-038-island-garden.md) |
| 039 | Island Beach | [DONE](./rooms/kq4-039-island-beach.md) | | 039 | Island Beach | ![pic_039_visual.png](./rooms/kq4-039-island-beach/pic_039_visual.png) [DONE](./rooms/kq4-039-island-beach/kq4-039-island-beach.md) |
| 040 | Island Beach (East) | [DONE](./rooms/kq4-040-island-beach-east.md) | | 040 | Island Beach (East) | ![pic_040_visual.png](./rooms/kq4-040-island-beach-east/pic_040_visual.png) [DONE](./rooms/kq4-040-island-beach-east/kq4-040-island-beach-east.md) |
| 041 | Island Shore | [DONE](./rooms/kq4-041-island-shore.md) | | 041 | Island Shore | ![pic_041_visual.png](./rooms/kq4-041-island-shore/pic_041_visual.png) [DONE](./rooms/kq4-041-island-shore/kq4-041-island-shore.md) |
| 042 | Fisherman's Cottage Interior | [DONE](./rooms/kq4-042-fishermans-shack-inside.md) | | 042 | Fisherman's Cottage Interior | ![pic_042_visual.png](./rooms/kq4-042-fishermans-shack-inside/pic_042_visual.png) [DONE](./rooms/kq4-042-fishermans-shack-inside/kq4-042-fishermans-shack-inside.md) |
| 043 | Desert Island | [DONE](./rooms/kq4-043-desert-island.md) | | 043 | Desert Island | ![pic_043_visual.png](./rooms/kq4-043-desert-island/pic_043_visual.png) [DONE](./rooms/kq4-043-desert-island/kq4-043-desert-island.md) |
| 044 | Inside Whale | [DONE](./rooms/kq4-044-inside-whale.md) | | 044 | Inside Whale | ![pic_044_visual.png](./rooms/kq4-044-inside-whale/pic_044_visual.png) [DONE](./rooms/kq4-044-inside-whale/kq4-044-inside-whale.md) |
| 045 | Genesta's Bed Chamber | [DONE](./rooms/kq4-045-genestas-bed-chamber.md) | | 045 | Genesta's Bed Chamber | ![pic_045_visual.png](./rooms/kq4-045-genestas-bed-chamber/pic_045_visual.png) [DONE](./rooms/kq4-045-genestas-bed-chamber/kq4-045-genestas-bed-chamber.md) |
| 046 | Tower Stairway | [DONE](./rooms/kq4-046-tower-stairway.md) | | 046 | Tower Stairway | ![pic_046_visual.png](./rooms/kq4-046-tower-stairway/pic_046_visual.png) [DONE](./rooms/kq4-046-tower-stairway/kq4-046-tower-stairway.md) |
| 047 | Genesta's Palace Entry Hall | [DONE](./rooms/kq4-047-genestas-palace-entry-hall.md) | | 047 | Genesta's Palace Entry Hall | ![pic_047_visual.png](./rooms/kq4-047-genestas-palace-entry-hall/pic_047_visual.png) [DONE](./rooms/kq4-047-genestas-palace-entry-hall/kq4-047-genestas-palace-entry-hall.md) |
| 048 | Ogre's Bedroom | [DONE](./rooms/kq4-048-ogres-bedroom.md) | | 048 | Ogre's Bedroom | ![pic_048_visual.png](./rooms/kq4-048-ogres-bedroom/pic_048_visual.png) [DONE](./rooms/kq4-048-ogres-bedroom/kq4-048-ogres-bedroom.md) |
| 049 | Ogre's Cottage | [DONE](./rooms/kq4-049-ogres-cottage.md) | | 049 | Ogre's Cottage | ![pic_049_visual.png](./rooms/kq4-049-ogres-cottage/pic_049_visual.png) [DONE](./rooms/kq4-049-ogres-cottage/kq4-049-ogres-cottage.md) |
| 050 | Ogress's Kitchen | [DONE](./rooms/kq4-050-ogress-kitchen.md) | | 050 | Ogress's Kitchen | ![pic_050_visual.png](./rooms/kq4-050-ogress-kitchen/pic_050_visual.png) [DONE](./rooms/kq4-050-ogress-kitchen/kq4-050-ogress-kitchen.md) |
| 051 | Ogre's Closet | [DONE](./rooms/kq4-051-ogres-closet.md) | | 051 | Ogre's Closet | ![pic_051_visual.png](./rooms/kq4-051-ogres-closet/pic_051_visual.png) [DONE](./rooms/kq4-051-ogres-closet/kq4-051-ogres-closet.md) |
| 052 | | N/A (doesn't exist) | | 052 | | N/A (doesn't exist) |
| 053 | Seven Dwarfs' Bedroom | [DONE](./rooms/kq4-053-seven-dwarfs-bedroom.md) | | 053 | Seven Dwarfs' Bedroom | ![pic_053_visual.png](./rooms/kq4-053-seven-dwarfs-bedroom/pic_053_visual.png) [DONE](./rooms/kq4-053-seven-dwarfs-bedroom/kq4-053-seven-dwarfs-bedroom.md) |
| 054 | Seven Dwarfs' Cottage | [DONE](./rooms/kq4-054-seven-dwarfs-cottage.md) | | 054 | Seven Dwarfs' Cottage | ![pic_054_visual.png](./rooms/kq4-054-seven-dwarfs-cottage/pic_054_visual.png) [DONE](./rooms/kq4-054-seven-dwarfs-cottage/kq4-054-seven-dwarfs-cottage.md) |
| 055 | Seven Dwarfs Diamond Mine | [DONE](./rooms/kq4-055-seven-dwarfs-diamond-mine.md) | | 055 | Seven Dwarfs Diamond Mine | ![pic_055_visual.png](./rooms/kq4-055-seven-dwarfs-diamond-mine/pic_055_visual.png) [DONE](./rooms/kq4-055-seven-dwarfs-diamond-mine/055-seven-dwarfs-diamond-mine.md) |
| 056 | Seven Dwarfs' Diamond Mine (West) | [DONE](./rooms/kq4-056-seven-dwarfs-diamond-mine-west.md) | | 056 | Seven Dwarfs' Diamond Mine (West) | ![pic_056_visual.png](./rooms/kq4-056-seven-dwarfs-diamond-mine-west/pic_056_visual.png) [DONE](./rooms/kq4-056-seven-dwarfs-diamond-mine-west/kq4-056-seven-dwarfs-diamond-mine-west.md) |
| 057 | Witches' Cave | [DONE](./rooms/kq4-057-witches-cave.md) | | 057 | Witches' Cave | ![pic_057_visual.png](./rooms/kq4-057-witch-cave/pic_057_visual.png) [DONE](./rooms/kq4-057-witch-cave/57-witch-cave.md) |
| 058 | Tower Organ Room | [DONE](./rooms/kq4-058-tower-organ-room.md) | | 058 | Tower Organ Room | ![pic_058_visual.png](./rooms/kq4-058-tower-organ-room/pic_058_visual.png) [DONE](./rooms/kq4-058-tower-organ-room/kq4-058-tower-organ-room.md) |
| 059 | Baby Nursery | [DONE](./rooms/kq4-059-baby-nursery.md) | | 059 | Baby Nursery | ![pic_059_visual.png](./rooms/kq4-059-baby-nursery/pic_059_visual.png) [DONE](./rooms/kq4-059-baby-nursery/kq4-059-baby-nursery.md) |
| 060 | Bedroom | [DONE](./rooms/kq4-060-bedroom.md) | | 060 | Bedroom | ![pic_060_visual.png](./rooms/kq4-060-bedroom/pic_060_visual.png) [DONE](./rooms/kq4-060-bedroom/kq4-060-bedroom.md) |
| 061 | Tower Stairs | [DONE](./rooms/kq4-061-tower-stairs.md) | | 061 | Tower Stairs | ![pic_061_visual.png](./rooms/kq4-061-tower-stairs/pic_061_visual.png) [DONE](./rooms/kq4-061-tower-stairs/kq4-061-tower-stairs.md) |
| 062 | Bedroom | [DONE](./rooms/kq4-062-bedroom.md) | | 062 | Bedroom | ![pic_062_visual.png](./rooms/kq4-062-bedroom/pic_062_visual.png) [DONE](./rooms/kq4-062-bedroom/kq4-062-bedroom.md) |
| 063 | Attic | [DONE](./rooms/kq4-063-attic.md) | | 063 | Attic | ![pic_063_visual.png](./rooms/kq4-063-attic/pic_063_visual.png) [DONE](./rooms/kq4-063-attic/kq4-063-attic.md) |
| 064 | Old Dining Room | [DONE](./rooms/kq4-064-old-dining-room.md) | | 064 | Old Dining Room | ![pic_064_visual.png](./rooms/kq4-064-old-dining-room/pic_064_visual.png) [DONE](./rooms/kq4-064-old-dining-room/kq4-064-old-dining-room.md) |
| 065 | Old Kitchen | [DONE](./rooms/kq4-065-old-kitchen.md) | | 065 | Old Kitchen | ![pic_065_visual.png](./rooms/kq4-065-old-kitchen/pic_065_visual.png) [DONE](./rooms/kq4-065-old-kitchen/kq4-065-old-kitchen.md) |
| 066 | Secret Tower | [DONE](./rooms/kq4-066-secret-tower.md) | | 066 | Secret Tower | ![pic_066_visual.png](./rooms/kq4-066-secret-tower/pic_066_visual.png) [DONE](./rooms/kq4-066-secret-tower/kq4-066-secret-tower.md) |
| 067 | The Parlor | [DONE](./rooms/kq4-067-the-parlor.md) | | 067 | The Parlor | ![pic_067_visual.png](./rooms/kq4-067-the-parlor/pic_067_visual.png) [DONE](./rooms/kq4-067-the-parlor/kq4-067-the-parlor.md) |
| 068 | The Foyer | [DONE](./rooms/kq4-068-the-foyer.md) | | 068 | The Foyer | ![pic_068_visual.png](./rooms/kq4-068-the-foyer/pic_068_visual.png) [DONE](./rooms/kq4-068-the-foyer/kq4-068-the-foyer.md) |
| 069 | The Crypt | [DONE](./rooms/kq4-069-the-crypt.md) | | 069 | The Crypt | ![pic_069_visual.png](./rooms/kq4-069-the-crypt/pic_069_visual.png) [DONE](./rooms/kq4-069-the-crypt/kq4-069-the-crypt.md) |
| 070 | Waterfall Cave | [DONE](./rooms/kq4-070-waterfall-cave.md) | | 070 | Waterfall Cave | ![pic_070_visual.png](./rooms/kq4-070-waterfall-cave/pic_070_visual.png) [DONE](./rooms/kq4-070-waterfall-cave/070-waterfall-cave.md) |
| 071 | Cave Entrance | [DONE](./rooms/kq4-071-cave-entrance.md) | | 071 | Cave Entrance | ![pic_071_visual.png](./rooms/kq4-071-cave-entrance/pic_071_visual.png) [DONE](./rooms/kq4-071-cave-entrance/kq4-071-cave-entrance.md) |
| 072 | Dark Cave Passage | [DONE](./rooms/kq4-072-dark-cave-passage.md) | | 072 | Dark Cave Passage | ![pic_072_visual.png](./rooms/kq4-072-dark-cave-passage/pic_072_visual.png) [DONE](./rooms/kq4-072-dark-cave-passage/kq4-072-dark-cave-passage.md) |
| 073 | Cave Exit | [DONE](./rooms/kq4-073-cave-exit.md) | | 073 | Cave Exit | ![pic_073_visual.png](./rooms/kq4-073-cave-exit/pic_073_visual.png) [DONE](./rooms/kq4-073-cave-exit/kq4-073-cave-exit.md) |
| 074 | Troll Cave | [DONE](./rooms/kq4-074-troll-cave.md) | | 074 | Troll Cave | ![pic_074_visual.png](./rooms/kq4-074-troll-cave/pic_074_visual.png) [DONE](./rooms/kq4-074-troll-cave/kq4-074-troll-cave.md) |
| 075 | Troll Cave Passage | [DONE](./rooms/kq4-075-troll-cave-passage.md) | | 075 | Troll Cave Passage | ![pic_075_visual.png](./rooms/kq4-075-troll-cave-passage/pic_075_visual.png) [DONE](./rooms/kq4-075-troll-cave-passage/kq4-075-troll-cave-passage.md) |
| 076 | Dark Chasm | [DONE](./rooms/kq4-076-dark-chasm.md) | | 076 | Dark Chasm | ![pic_076_visual.png](./rooms/kq4-076-dark-chasm/pic_076_visual.png) [DONE](./rooms/kq4-076-dark-chasm/kq4-076-dark-chasm.md) |
| 077 | Swamp | [DONE](./rooms/kq4-077-swamp.md) | | 077 | Swamp | ![pic_077_visual.png](./rooms/kq4-077-swamp/pic_077_visual.png) [DONE](./rooms/kq4-077-swamp/kq4-077-swamp.md) |
| 078 | Swamp Island | [DONE](./rooms/kq4-078-swamp-island.md) | | 078 | Swamp Island | ![pic_078_visual.png](./rooms/kq4-078-swamp-island/pic_078_visual.png) [DONE](./rooms/kq4-078-swamp-island/kq4-078-swamp-island.md) |
| 079 | Mountain Path to Dark Castle | [DONE](./rooms/kq4-079-mountain-path-to-dark-castle.md) | | 079 | Mountain Path to Dark Castle | ![pic_079_visual.png](./rooms/kq4-079-mountain-path-to-dark-castle/pic_079_visual.png) [DONE](./rooms/kq4-079-mountain-path-to-dark-castle/kq4-079-mountain-path-to-dark-castle.md) |
| 080 | Lolotte's Castle Entrance | [DONE](./rooms/kq4-080-lolottes-castle-entrance.md) | | 080 | Lolotte's Castle Entrance | ![pic_080_visual.png](./rooms/kq4-080-lolottes-castle-entrance/pic_080_visual.png) [DONE](./rooms/kq4-080-lolottes-castle-entrance/kq4-080-lolottes-castle-entrance.md) |
| 081 | Edgar's Tower Bedroom | [DONE](./rooms/kq4-081-edgars-tower-bedroom.md) | | 081 | Edgar's Tower Bedroom | ![pic_081_visual.png](./rooms/kq4-081-edgars-tower-bedroom/pic_081_visual.png) [DONE](./rooms/kq4-081-edgars-tower-bedroom/kq4-081-edgars-tower-bedroom.md) |
| 082 | Lolotte's Tower Bedroom | [DONE](./rooms/kq4-082-lolottes-tower-bedroom.md) | | 082 | Lolotte's Tower Bedroom | ![pic_082_visual.png](./rooms/kq4-082-lolottes-tower-bedroom/pic_082_visual.png) [DONE](./rooms/kq4-082-lolottes-tower-bedroom/kq4-082-lolottes-tower-bedroom.md) |
| 083 | Castle Dungeon Cell | [DONE](./rooms/kq4-083-castle-dungeon-cell.md) | | 083 | Castle Dungeon Cell | ![pic_083_visual.png](./rooms/kq4-083-castle-dungeon-cell/pic_083_visual.png) [DONE](./rooms/kq4-083-castle-dungeon-cell/kq4-083-castle-dungeon-cell.md) |
| 084 | Cottage Front | [DONE](./rooms/kq4-084-cottage-front.md) | | 084 | Cottage Front | ![pic_084_visual.png](./rooms/kq4-084-cottage-front/pic_084_visual.png) [DONE](./rooms/kq4-084-cottage-front/kq4-084-cottage-front.md) |
| 085 | Dark Tower Stairs | [DONE](./rooms/kq4-085-dark-tower-stairs.md) | | 085 | Dark Tower Stairs | ![pic_085_visual.png](./rooms/kq4-085-dark-tower-stairs/pic_085_visual.png) [DONE](./rooms/kq4-085-dark-tower-stairs/kq4-085-dark-tower-stairs.md) |
| 086 | Dim Hallway (West End) | [DONE](./rooms/kq4-086-dim-hallway-west-end.md) | | 086 | Dim Hallway (West End) | ![pic_086_visual.png](./rooms/kq4-086-dim-hallway-west-end/pic_086_visual.png) [DONE](./rooms/kq4-086-dim-hallway-west-end/kq4-086-dim-hallway-west-end.md) |
| 087 | East End of Hallway | [DONE](./rooms/kq4-087-east-end-of-hallway.md) | | 087 | East End of Hallway | ![pic_087_visual.png](./rooms/kq4-087-east-end-of-hallway/pic_087_visual.png) [DONE](./rooms/kq4-087-east-end-of-hallway/kq4-087-east-end-of-hallway.md) |
| 088 | Stone Tower Stairs | [DONE](./rooms/kq4-088-stone-tower-stairs.md) | | 088 | Stone Tower Stairs | ![pic_088_visual.png](./rooms/kq4-088-stone-tower-stairs/pic_088_visual.png) [DONE](./rooms/kq4-088-stone-tower-stairs/kq4-088-stone-tower-stairs.md) |
| 089 | Castle Kitchen | [DONE](./rooms/kq4-089-castle-kitchen.md) | | 089 | Castle Kitchen | ![pic_089_visual.png](./rooms/kq4-089-castle-kitchen/pic_089_visual.png) [DONE](./rooms/kq4-089-castle-kitchen/kq4-089-castle-kitchen.md) |
| 090 | West Tower Bottom | [DONE](./rooms/kq4-090-west-tower-bottom.md) | | 090 | West Tower Bottom | ![pic_090_visual.png](./rooms/kq4-090-west-tower-bottom/pic_090_visual.png) [DONE](./rooms/kq4-090-west-tower-bottom/kq4-090-west-tower-bottom.md) |
| 091 | Castle Dining Room | [DONE](./rooms/kq4-091-castle-dining-room.md) | | 091 | Castle Dining Room | ![pic_091_visual.png](./rooms/kq4-091-castle-dining-room/pic_091_visual.png) [DONE](./rooms/kq4-091-castle-dining-room/kq4-091-castle-dining-room.md) |
| 092 | Lolotte's Throne Room | [DONE](./rooms/kq4-092-lolottes-throne-room.md) | | 092 | Lolotte's Throne Room | ![pic_092_visual.png](./rooms/kq4-092-lolottes-throne-room/pic_092_visual.png) [DONE](./rooms/kq4-092-lolottes-throne-room/kq4-092-lolottes-throne-room.md) |
| 093 | Bottom of East Tower | [DONE](./rooms/kq4-093-bottom-of-east-tower.md) | | 093 | Bottom of East Tower | ![pic_093_visual.png](./rooms/kq4-093-bottom-of-east-tower/pic_093_visual.png) [DONE](./rooms/kq4-093-bottom-of-east-tower/kq4-093-bottom-of-east-tower.md) |
| 094 | Unicorn Stable | [DONE](./rooms/kq4-094-unicorn-stable.md) | | 094 | Unicorn Stable | ![pic_094_visual.png](./rooms/kq4-094-unicorn-stable/pic_094_visual.png) [DONE](./rooms/kq4-094-unicorn-stable/kq4-094-unicorn-stable.md) |
| 095 | Fisherman's Pier | [DONE](./rooms/kq4-095-fishermans-pier.md) | | 095 | Fisherman's Pier | ![pic_095_visual.png](./rooms/kq4-095-fishermans-pier/pic_095_visual.png) [DONE](./rooms/kq4-095-fishermans-pier/kq4-095-fishermans-pier.md) |
| 096 | | N/A (doesn't exist) | | 096 | | N/A (doesn't exist) |
| 097 | | N/A (doesn't exist) | | 097 | | N/A (doesn't exist) |
| 098 | Transitional Room | [DONE](./rooms/kq4-098-transitional-room.md) | | 098 | Transitional Room | [DONE](./rooms/kq4-098-transitional-room/kq4-098-transitional-room.md) |
| 099 | Transitional Room | [DONE](./rooms/kq4-099-transitional-room.md) | | 099 | Transitional Room | [DONE](./rooms/kq4-099-transitional-room/kq4-099-transitional-room.md) |
## Credits ## Credits

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

566
extract_sci_pic.py Normal file
View File

@@ -0,0 +1,566 @@
#!/usr/bin/env python3
"""
SCI Picture Resource Extractor
Extracts and renders background images (PIC resources) from Sierra's Creative Interpreter (SCI) games.
Supports SCI0 format (King's Quest IV).
"""
import os
import sys
from pathlib import Path
from typing import Tuple, Optional
from collections import deque
import numpy as np
from PIL import Image
PIC_OP_SET_COLOR = 0xf0
PIC_OP_DISABLE_VISUAL = 0xf1
PIC_OP_SET_PRIORITY = 0xf2
PIC_OP_DISABLE_PRIORITY = 0xf3
PIC_OP_RELATIVE_PATTERNS = 0xf4
PIC_OP_RELATIVE_MEDIUM_LINES = 0xf5
PIC_OP_RELATIVE_LONG_LINES = 0xf6
PIC_OP_RELATIVE_SHORT_LINES = 0xf7
PIC_OP_FILL = 0xf8
PIC_OP_SET_PATTERN = 0xf9
PIC_OP_ABSOLUTE_PATTERNS = 0xfa
PIC_OP_SET_CONTROL = 0xfb
PIC_OP_DISABLE_CONTROL = 0xfc
PIC_OP_RELATIVE_MEDIUM_PATTERNS = 0xfd
PIC_OP_OPX = 0xfe
PIC_OP_END = 0xff
WIDTH, HEIGHT = 320, 190
PATTERN_FLAG_RECTANGLE = 0x10
PATTERN_FLAG_USE_PATTERN = 0x20
CIRCLES = [
[0x80],
[0x4e, 0x40],
[0x73, 0xef, 0xbe, 0x70],
[0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x00],
[0x1c, 0x1f, 0xcf, 0xfb, 0xfe, 0xff, 0xbf, 0xef, 0xf9, 0xfc, 0x1c],
[0x0e, 0x03, 0xf8, 0x7f, 0xc7, 0xfc, 0xff, 0xef, 0xfe, 0xff, 0xe7, 0xfc, 0x7f, 0xc3, 0xf8, 0x1f, 0x00],
[0x0f, 0x80, 0xff, 0x87, 0xff, 0x1f, 0xfc, 0xff, 0xfb, 0xff, 0xef, 0xff, 0xbf, 0xfe, 0xff, 0xf9, 0xff, 0xc7, 0xff, 0x0f, 0xf8, 0x0f, 0x80],
[0x07, 0xc0, 0x1f, 0xf0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf0, 0x07, 0xc0],
]
JUNQ = [0x20, 0x94, 0x02, 0x24, 0x90, 0x82, 0xa4, 0xa2, 0x82, 0x09, 0x0a, 0x22, 0x12, 0x10, 0x42, 0x14, 0x91, 0x4a, 0x91, 0x11, 0x08, 0x12, 0x25, 0x10, 0x22, 0xa8, 0x14, 0x24, 0x00, 0x50, 0x24, 0x04]
JUNQINDEX = [0x00, 0x18, 0x30, 0xc4, 0xdc, 0x65, 0xeb, 0x48, 0x60, 0xbd, 0x89, 0x05, 0x0a, 0xf4, 0x7d, 0x7d, 0x85, 0xb0, 0x8e, 0x95, 0x1f, 0x22, 0x0d, 0xdf, 0x2a, 0x78, 0xd5, 0x73, 0x1c, 0xb4, 0x40, 0xa1, 0xb9, 0x3c, 0xca, 0x58, 0x92, 0x34, 0xcc, 0xce, 0xd7, 0x42, 0x90, 0x0f, 0x8b, 0x7f, 0x32, 0xed, 0x5c, 0x9d, 0xc8, 0x99, 0xad, 0x4e, 0x56, 0xa6, 0xf7, 0x68, 0xb7, 0x25, 0x82, 0x37, 0x3a, 0x51, 0x69, 0x26, 0x38, 0x52, 0x9e, 0x9a, 0x4f, 0xa7, 0x43, 0x10, 0x80, 0xee, 0x3d, 0x59, 0x35, 0xcf, 0x79, 0x74, 0xb5, 0xa2, 0xb1, 0x96, 0x23, 0xe0, 0xbe, 0x05, 0xf5, 0x6e, 0x19, 0xc5, 0x66, 0x49, 0xf0, 0xd1, 0x54, 0xa9, 0x70, 0x4b, 0xa4, 0xe2, 0xe6, 0xe5, 0xab, 0xe4, 0xd2, 0xaa, 0x4c, 0xe3, 0x06, 0x6f, 0xc6, 0x4a, 0xa4, 0x75, 0x97, 0xe1]
EGA_COLORS = [
(0x00, 0x00, 0x00), (0x00, 0x00, 0xA0), (0x00, 0xA0, 0x00), (0x00, 0xA0, 0xA0),
(0xA0, 0x00, 0x00), (0xA0, 0x00, 0xA0), (0xA0, 0x50, 0x00), (0xA0, 0xA0, 0xA0),
(0x50, 0x50, 0x50), (0x50, 0x50, 0xFF), (0x00, 0xFF, 0x50), (0x50, 0xFF, 0xFF),
(0xFF, 0x50, 0x50), (0xFF, 0x50, 0xFF), (0xFF, 0xFF, 0x50), (0xFF, 0xFF, 0xFF),
]
DEFAULT_PALETTE = [
(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7),
(8, 8), (9, 9), (10, 10), (11, 11), (12, 12), (13, 13), (14, 14), (8, 8),
(8, 8), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (8, 8),
(8, 8), (15, 9), (15, 10), (15, 11), (15, 12), (15, 13), (15, 14), (15, 15),
(0, 8), (9, 1), (2, 10), (3, 11), (4, 12), (5, 13), (6, 14), (8, 8),
]
class PictureRenderer:
VISUAL = 1
PRIORITY = 2
CONTROL = 4
def __init__(self):
self.visual = np.full((HEIGHT, WIDTH), 0x0f, dtype=np.uint8)
self.priority = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
self.control = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
self.aux = np.zeros((HEIGHT, WIDTH), dtype=np.uint8)
self.palettes = [list(DEFAULT_PALETTE) for _ in range(4)]
self.locked = [0] * 40
self.reset_state()
def reset_state(self):
self.palette_number = 0
self.palette_offset = 0
self.ega_color = (0, 0)
self.current_priority = 0
self.current_control = 0
self.draw_enable = self.VISUAL
self.palette_to_draw = 0
self.pattern_size = 0
self.pattern_nr = 0
self.pattern_use_pattern = False
self.pattern_is_rect = False
def is_white(self):
return self.ega_color[0] == 15 and self.ega_color[1] == 15
def get_aux_set(self):
aux = self.draw_enable
if self.ega_color[0] == 15 and self.ega_color[1] == 15:
aux &= ~self.VISUAL
if self.current_priority == 0:
aux &= ~self.PRIORITY
if self.current_control == 0:
aux &= ~self.CONTROL
return aux
def plot_pixel(self, x: int, y: int):
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
aux_set = self.get_aux_set()
if self.draw_enable & self.VISUAL:
# EGA dithering: color1 on odd pixels, color2 on even pixels
color_idx = self.ega_color[0] if ((x ^ y) & 1) else self.ega_color[1]
self.visual[y, x] = color_idx
if self.draw_enable & self.PRIORITY:
self.priority[y, x] = self.current_priority
if self.draw_enable & self.CONTROL:
self.control[y, x] = self.current_control
self.aux[y, x] |= aux_set
def draw_line(self, x1: int, y1: int, x2: int, y2: int):
dx = abs(x2 - x1)
dy = abs(y2 - y1)
sx = 1 if x1 < x2 else -1
sy = 1 if y1 < y2 else -1
err = dx - dy
while True:
self.plot_pixel(x1, y1)
if x1 == x2 and y1 == y2:
break
e2 = 2 * err
if e2 > -dy:
err -= dy
x1 += sx
if e2 < dx:
err += dx
y1 += sy
def draw_pattern(self, x: int, y: int, size: int, pattern_nr: int, use_pattern: bool, is_rect: bool):
wsize = size
x_max, y_max = WIDTH - 1, HEIGHT - 1
if x < wsize:
x = wsize
if x + wsize > x_max:
x = x_max - wsize
if y < wsize:
y = wsize
if y + wsize > y_max:
y = y_max - wsize
if pattern_nr >= len(JUNQINDEX):
return
junqbit = JUNQINDEX[pattern_nr]
if is_rect:
for ly in range(y - wsize, y + wsize + 1):
for lx in range(x - wsize, x + wsize + 2):
if use_pattern:
if (JUNQ[junqbit >> 3] >> (7 - (junqbit & 7))) & 1:
self.plot_pixel(lx, ly)
junqbit = (junqbit + 1) & 0xFF
else:
self.plot_pixel(lx, ly)
else:
circlebit = 0
circle_data = CIRCLES[size]
circle_data_bits = len(circle_data) * 8
for ly in range(y - wsize, y + wsize + 1):
for lx in range(x - wsize, x + wsize + 2):
circle_on = False
if circlebit < circle_data_bits:
circle_on = bool((circle_data[circlebit >> 3] >> (7 - (circlebit & 7))) & 1)
if circle_on:
if use_pattern:
if (JUNQ[junqbit >> 3] >> (7 - (junqbit & 7))) & 1:
self.plot_pixel(lx, ly)
junqbit = (junqbit + 1) & 0xFF
else:
self.plot_pixel(lx, ly)
circlebit += 1
def _fill_bounds(self, x: int, y: int, draw_enable: int) -> bool:
"""Check if (x, y) is a fill boundary, using the given draw_enable flags.
Returns True if the pixel is a boundary (should NOT be filled).
Matches SCICompanion's FILL_BOUNDS macro:
(dwDrawEnable & aux) && !(visual_enabled && pixel_is_white)
"""
aux = self.aux[y, x]
overlap = draw_enable & aux
if overlap:
# There IS something drawn here that overlaps with what we want to draw.
# But if visual is enabled and the pixel is white, treat it as empty (not a boundary).
if (draw_enable & self.VISUAL) and (self.visual[y, x] == 0x0f):
return False
return True
return False
def _ok_to_fill(self, x: int, y: int, draw_enable: int) -> bool:
if not (0 <= x < WIDTH and 0 <= y < HEIGHT):
return False
return not self._fill_bounds(x, y, draw_enable)
def flood_fill(self, start_x: int, start_y: int):
if not (0 <= start_x < WIDTH and 0 <= start_y < HEIGHT):
return
# Compute auxSet from the color/priority/control and draw_enable.
# Then replace draw_enable with auxSet (matching SCICompanion behavior).
aux_set = self.get_aux_set()
draw_enable = aux_set
# Guard against filling with pure white (would hang/infinite loop).
if self.is_white():
return
# If no screen is being drawn to, bail.
if draw_enable == 0:
return
# Check if starting point is already a boundary.
if self._fill_bounds(start_x, start_y, draw_enable):
return
# Use a stack (LIFO) to match SCICompanion's qstore/qretrieve behavior.
stack = [(start_x, start_y)]
while stack:
x, y = stack.pop()
if not self._ok_to_fill(x, y, draw_enable):
continue
# Plot the pixel
if draw_enable & self.VISUAL:
self.visual[y, x] = self.ega_color[0] if ((x ^ y) & 1) else self.ega_color[1]
if draw_enable & self.PRIORITY:
self.priority[y, x] = self.current_priority
if draw_enable & self.CONTROL:
self.control[y, x] = self.current_control
self.aux[y, x] |= aux_set
if y > 0 and self._ok_to_fill(x, y - 1, draw_enable):
stack.append((x, y - 1))
if x > 0 and self._ok_to_fill(x - 1, y, draw_enable):
stack.append((x - 1, y))
if x < WIDTH - 1 and self._ok_to_fill(x + 1, y, draw_enable):
stack.append((x + 1, y))
if y < HEIGHT - 1 and self._ok_to_fill(x, y + 1, draw_enable):
stack.append((x, y + 1))
def read_abs_coords(self, data: bytes, i: int) -> Tuple[int, int, int]:
prefix = data[i]
x = data[i + 1] | ((prefix & 0xF0) << 4)
y = data[i + 2] | ((prefix & 0x0F) << 8)
return x, y, i + 3
def render(self, data: bytes) -> Tuple[Image.Image, Image.Image, Image.Image]:
self.visual.fill(0x0f)
self.priority.fill(0)
self.control.fill(0)
self.aux.fill(0)
self.palettes = [list(DEFAULT_PALETTE) for _ in range(4)]
self.locked = [0] * 40
self.reset_state()
i = 0
x, y = 0, 0
while i < len(data):
opcode = data[i]
if opcode >= 0xF0:
i += 1
if opcode == PIC_OP_END:
break
elif opcode == PIC_OP_SET_COLOR:
color_code = data[i]
i += 1
# Palette number and offset within palette
palette_num = color_code // 40
palette_offset = color_code % 40
# If palette_num is 0, use the global palette_to_draw instead
palette_to_use = palette_num if palette_num != 0 else self.palette_to_draw
# Store for UI/state tracking
self.palette_number = palette_num
self.palette_offset = palette_offset
# Look up the actual color from the palette
if self.locked[palette_offset]:
self.ega_color = self.palettes[0][palette_offset]
else:
self.ega_color = self.palettes[palette_to_use][palette_offset]
self.draw_enable |= self.VISUAL
elif opcode == PIC_OP_DISABLE_VISUAL:
self.draw_enable &= ~self.VISUAL
elif opcode == PIC_OP_SET_PRIORITY:
self.current_priority = data[i] & 0x0F
i += 1
self.draw_enable |= self.PRIORITY
elif opcode == PIC_OP_DISABLE_PRIORITY:
self.draw_enable &= ~self.PRIORITY
elif opcode == PIC_OP_SET_CONTROL:
self.current_control = data[i]
i += 1
self.draw_enable |= self.CONTROL
elif opcode == PIC_OP_DISABLE_CONTROL:
self.draw_enable &= ~self.CONTROL
elif opcode == PIC_OP_RELATIVE_MEDIUM_LINES:
x, y, i = self.read_abs_coords(data, i)
while i < len(data) and data[i] < 0xF0:
by = data[i]
bx = data[i + 1]
i += 2
dy = (by & 0x7F) if not (by & 0x80) else -(by & 0x7F)
dx = bx if bx < 128 else bx - 256
self.draw_line(x, y, x + dx, y + dy)
x += dx
y += dy
elif opcode == PIC_OP_RELATIVE_LONG_LINES:
x, y, i = self.read_abs_coords(data, i)
while i < len(data) and data[i] < 0xF0:
x2, y2, i = self.read_abs_coords(data, i)
self.draw_line(x, y, x2, y2)
x, y = x2, y2
elif opcode == PIC_OP_RELATIVE_SHORT_LINES:
x, y, i = self.read_abs_coords(data, i)
while i < len(data) and data[i] < 0xF0:
b = data[i]
i += 1
dx = ((b >> 4) & 0x7) if not (b & 0x80) else -((b >> 4) & 0x7)
dy = (b & 0x7) if not (b & 0x08) else -(b & 0x7)
self.draw_line(x, y, x + dx, y + dy)
x += dx
y += dy
elif opcode == PIC_OP_FILL:
while i < len(data) and data[i] < 0xF0:
fx, fy, i = self.read_abs_coords(data, i)
self.flood_fill(fx, fy)
elif opcode == PIC_OP_RELATIVE_PATTERNS:
pattern_nr_byte = 0
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
pattern_nr_byte = data[i]
i += 1
x, y, i = self.read_abs_coords(data, i)
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
while i < len(data) and data[i] < 0xF0:
pattern_nr_byte = 0
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
pattern_nr_byte = data[i]
i += 1
# Relative short coords for pattern
b = data[i]
i += 1
dx = ((b >> 4) & 0x7) if not (b & 0x80) else -((b >> 4) & 0x7)
dy = (b & 0x7) if not (b & 0x08) else -(b & 0x7)
x += dx
y += dy
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
elif opcode == PIC_OP_ABSOLUTE_PATTERNS:
while i < len(data) and data[i] < 0xF0:
pattern_nr_byte = 0
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
pattern_nr_byte = data[i]
i += 1
x, y, i = self.read_abs_coords(data, i)
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
elif opcode == PIC_OP_RELATIVE_MEDIUM_PATTERNS:
pattern_nr_byte = 0
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
pattern_nr_byte = data[i]
i += 1
x, y, i = self.read_abs_coords(data, i)
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
while i < len(data) and data[i] < 0xF0:
pattern_nr_byte = 0
if self.pattern_use_pattern and i < len(data) and data[i] < 0xF0:
pattern_nr_byte = data[i]
i += 1
by = data[i]
bx = data[i + 1]
i += 2
dy = (by & 0x7F) if not (by & 0x80) else -(by & 0x7F)
dx = bx if bx < 128 else bx - 256
x += dx
y += dy
self.draw_pattern(x, y, self.pattern_size, (pattern_nr_byte >> 1) & 0x7f, self.pattern_use_pattern, self.pattern_is_rect)
elif opcode == PIC_OP_SET_PATTERN:
if i < len(data) and data[i] < 0xF0:
val = data[i]
i += 1
val &= 0x37 # Mask to valid bits as SCICompanion does
self.pattern_size = val & 0x07
self.pattern_is_rect = bool(val & PATTERN_FLAG_RECTANGLE)
self.pattern_use_pattern = bool(val & PATTERN_FLAG_USE_PATTERN)
elif opcode == PIC_OP_OPX:
if i >= len(data):
break
ext = data[i]
i += 1
if ext == 0x00:
# PIC_OPX_SET_PALETTE_ENTRY: variable length pairs of (index, color)
while i < len(data) and data[i] < 0xF0:
b_index = data[i]
b_color = data[i + 1]
i += 2
pal_num = b_index // 40
pal_offset = b_index % 40
if pal_num < 4:
# color1 is high nibble, color2 is low nibble
color1 = (b_color >> 4) & 0x0f
color2 = b_color & 0x0f
self.palettes[pal_num][pal_offset] = (color1, color2)
if pal_num == 0:
self.locked[pal_offset] = 0xff
elif ext == 0x01:
# PIC_OPX_SET_PALETTE: 1 byte palette number + 40 color bytes
pal_num = data[i]
i += 1
for j in range(40):
b_color = data[i]
i += 1
color1 = (b_color >> 4) & 0x0f
color2 = b_color & 0x0f
self.palettes[pal_num][j] = (color1, color2)
# If this palette is the one currently in use, update egaColor
if pal_num == self.palette_number:
self.ega_color = self.palettes[pal_num][self.palette_offset]
elif ext == 0x08:
# PIC_OPX_SET_PRIORITY_TABLE: 14 bytes
i += 14
else:
# Unknown extended opcode, try to skip gracefully
pass
else:
break
visual_img = Image.new('RGB', (WIDTH, HEIGHT))
for py in range(HEIGHT):
for px in range(WIDTH):
visual_img.putpixel((px, py), EGA_COLORS[self.visual[py, px]])
priority_img = Image.fromarray(self.priority * 17, mode='L')
control_img = Image.fromarray(self.control * 17, mode='L')
return visual_img, priority_img, control_img
class SCIPictureExtractor:
def __init__(self, game_dir: str):
self.game_dir = Path(game_dir)
import importlib.util
spec = importlib.util.spec_from_file_location(
"extract_sci_text",
self.game_dir.parent.parent / "extract_sci_text.py"
)
if spec and spec.loader:
self.extractor_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(self.extractor_module)
self.extractor = self.extractor_module.SCIResourceExtractor(game_dir)
else:
raise ImportError("Could not load extract_sci_text module")
def extract_pic(self, pic_number: int, output_dir: str = ".") -> Optional[Tuple[str, str, str]]:
resources = self.extractor.read_resource_map()
pic_resources = [r for r in resources if r['type'] == 1 and r['number'] == pic_number]
if not pic_resources:
print(f"PIC {pic_number} not found")
return None
res = pic_resources[0]
print(f"Extracting PIC {pic_number} from package {res['package']}...")
header = self.extractor.read_resource_header(res['package'], res['offset'])
if not header:
print(f"Failed to read header for PIC {pic_number}")
return None
print(f" Compression: {header['method_name']}, Size: {header['decompressed_size']} bytes")
data = self.extractor.extract_resource_data(
res['package'],
res['offset'],
header['compressed_size'],
header['decompressed_size'],
header['method']
)
if not data:
print(f"Failed to extract data for PIC {pic_number}")
return None
renderer = PictureRenderer()
visual_img, priority_img, control_img = renderer.render(data)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
base_name = f"pic_{pic_number:03d}"
visual_path = output_path / f"{base_name}_visual.png"
priority_path = output_path / f"{base_name}_priority.png"
control_path = output_path / f"{base_name}_control.png"
visual_img.save(visual_path)
priority_img.save(priority_path)
control_img.save(control_path)
print(f" Saved: {visual_path}")
return (str(visual_path), str(priority_path), str(control_path))
def main():
import argparse
parser = argparse.ArgumentParser(description="Extract SCI picture resources")
parser.add_argument("--pic", type=int, help="Specific PIC number to extract")
parser.add_argument("--output", "-o", default="pics", help="Output directory")
parser.add_argument("--game-dir", "-g", default="King's Quest IV - The Perils of Rosella (1988)/KQ4",
help="Game directory")
args = parser.parse_args()
if not os.path.exists(args.game_dir):
print(f"Error: Game directory not found: {args.game_dir}")
sys.exit(1)
print(f"Extracting pictures from: {args.game_dir}")
extractor = SCIPictureExtractor(args.game_dir)
if args.pic is not None:
extractor.extract_pic(args.pic, args.output)
else:
resources = extractor.extractor.read_resource_map()
pic_resources = sorted(set(r['number'] for r in resources if r['type'] == 1))
print(f"Found {len(pic_resources)} PIC resources")
for pic_num in pic_resources:
extractor.extract_pic(pic_num, args.output)
print(f"\nExtraction complete! Images saved to {args.output}/")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Some files were not shown because too many files have changed in this diff Show More