Jump to content

Zenonia .pzx file


Kazuma
Go to solution Solved by piken,

Recommended Posts

Hi, can someone help me make a bms script that could convert a pzx file to png or jpg? I tried making on my own using this guide (https://www.psxhax.com/threads/quickbms-making-bms-scripts-tutorial-by-thattruestruggle.2190/) but I couldn't find the header and offset. 

Update:

I tried decompressing 005.pzx on face zip using someone's script (https://ya4r.net/forum/index.php?id=156605&page=1&newup) assuming that it is also a zlib since it just for the same game and it converted to 00000135.dat.

face.zip

dat&bms.zip

Edited by Kazuma
Update
Link to comment
Share on other sites

I don't have your answer, just an observation. Looking at the corresponding 00000135.dat sample you shared via the XeNTaX discord (which I recommend amending into the .zip file above to facilitate broader potential assistance here, plus a link directly to the .bms file you used), it's clear that the data isn't completely decompressed yet into a regular linear image, as there are variable size rows. What's curious is instances of {FE FF 00 00 ## 80}, and that often the width varies dependent on the 3rd and 5th byte, like they might be padding values for the left and right edges of each row. 🤔

image.png.cce5448de45f4e78cddacb05821ebac2.png

If you wrap all the byte spans between FE FF into rows, then you notice something more graphicsish. Though, without a larger image sample and correct palette, I can't tell what I'm looking at. 🤷

image.png.8218aeb5fe9f20b9c9049c1630332640.png

Edited by piken
Link to comment
Share on other sites

13 minutes ago, piken said:

I don't have your answer, just an observation. Looking at the corresponding 00000135.dat sample you shared via the XeNTaX discord (which I recommend amending into the .zip file above to facilitate broader potential assistance here, plus a link directly to the .bms file you used), it's clear that the data isn't completely decompressed yet into a regular linear image, as there are variable size rows. What's curious is instances of {FE FF 00 00 ## 80}, and that often the width varies dependent on the 3rd and 5th byte, like they might be padding values for the left and right edges of each row. 🤔

image.png.cce5448de45f4e78cddacb05821ebac2.png

If you wrap all the byte spans between FE FF into rows, then you notice something more graphicsish. Though, without a larger image sample and correct palette, I can't tell what I'm looking at. 🤷

image.png.8218aeb5fe9f20b9c9049c1630332640.png

Thank you! That's an interesting progress, the picture shown looks like her. I'm not sure if this is the color palette but some pzx file has the same name file included but with different extension (.mpl).

82EA73C1-1392-45A2-B715-5E43C824AC25.jpg

map.zip

Link to comment
Share on other sites

  • Solution

> the picture shown looks like...

Indeed, with that reference image, I could tell byte offset 2 in each row header holds the left padding value, which helps the staff look like a staff.

image.png.da2f7c9099fbb23d27e755ff5410c573.png

struct RowHeader
{
    uint8 signature0; // 0xFE
    uint8 signature1; // 0xFE
    uint8 leftRowPadding;
    uint8 unknown; // Zero
    uint8 maybeRightRowPadding;
}

> some pzx file has the same name file included but with different extension (.mpl).

Hmm, it doesn't look like a palette trying any of the usual formats (r5g5b5, b8g8r8a8, r8g8b8), but I see how the extension "mpl" and small file size (<1024 bytes) hints at that. Perhaps they too are compressed.

image.png.5c8f3335bc977e83c4cf9f4404418668.png

Well, I'll defer the palette investigation to you, but here's some wonderfully hacky non-idiomatic Python code (3.9.17) to wrap the input data and output to 8bpp grayscale images:

import sys
import os
from PIL import Image # Depends on Pillow (I used 10.0.0)

if len(sys.argv) < 6:
    print(
        "Need the following parameters:\n"
        "- input file name (e.g. foo.bin)\n"
        "- output file byte width (e.g. 256)\n"
        "- marker bytes to find and split input into rows (e.g. FE,FF)\n"
        "- byte size of each row header in input to read first pixel\n"
        "- left padding offset (where the left padding value resides each block)\n"
        "\n"
        "    python wrapDataOnMarker.py filename.dat 256 FE,FF 6 2\n"
        "\n"
        "Pass left padding offset = 0 for none.\n"
        "Note this only works for 8bpp pixels.\n"
        "Produces output \"filename.dat.wrapped\" and \"filename.dat.png\"."
    )
    exit()
#endif

inputFilename = sys.argv[1]
outputDataFilename = inputFilename + ".wrapped"
outputImageFilename = inputFilename + ".png"
maximumWidth = int(sys.argv[2])
splitMarkerText = sys.argv[3].split(',')
rowHeaderByteSize = int(sys.argv[4])
leftPaddingOffset = int(sys.argv[5])

if rowHeaderByteSize < len(splitMarkerText):
    raise ValueError("The row header byte size needs to be at least the size of the split marker.")
#endif

if leftPaddingOffset >= rowHeaderByteSize:
    raise ValueError("The left padding must reside within the row header byte size.")
#endif

# Convert comma separated hex codes to byte array.
# (and no, I don't really know Python, as there is likely a shorter way)
splitMarkerText = sys.argv[3].split(',')
splitMarkerBytes = bytearray()
for c in splitMarkerText:
    splitMarkerBytes.append(int(c, 16))
#endfor
splitMarkerBytesLength = len(splitMarkerBytes)

print(f"Input filename: {inputFilename}")
print(f"Maximum width: {maximumWidth}")
print(f"Split marker: {splitMarkerText}")
wrappedData = bytearray()

# Split every occurence of the marker into a separate row.
with open(inputFilename, 'rb') as f:
    fileData = f.read()
    fileLength = len(fileData)
    print(f"File length: {fileLength}")
    previousWrappedStart = 0
    rowCount = 0

    fileOffset = 0
    paddingWidthLeft = 0
    lastFileOffset = max(fileLength - rowHeaderByteSize, 0)
    while fileOffset < fileLength:
        #print(fileOffset, fileData[fileOffset : fileOffset + splitMarkerBytesLength])

        if fileData[fileOffset : fileOffset + splitMarkerBytesLength] == splitMarkerBytes:
            # Beginning new row since match was found...

            # Compute the 3 spans of the row: left padding | interior pixel width | right padding.
            byteSpanWidth = fileOffset - previousWrappedStart
            if leftPaddingOffset > 0 and fileOffset + leftPaddingOffset < fileLength:
                paddingWidthLeft = fileData[fileOffset + leftPaddingOffset]
            #endif
            clampedPaddingWidthLeft = min(paddingWidthLeft, maximumWidth)
            interiorWidth = byteSpanWidth - rowHeaderByteSize
            clampedInteriorWidth = min(interiorWidth, maximumWidth)
            clampedWidthLeftAndInterior = min(clampedInteriorWidth + clampedPaddingWidthLeft, maximumWidth)
            clampedInteriorWidth = clampedWidthLeftAndInterior - clampedPaddingWidthLeft
            clampedPaddingWidthRight = maximumWidth - clampedWidthLeftAndInterior

            print(f"matching byte offset: {fileOffset} previous byte offset: {previousWrappedStart}, byte span width: {byteSpanWidth}")
            print(f"  emitting row: {rowCount}, padding left: {paddingWidthLeft}, padding right: {clampedPaddingWidthRight}, interior width: {interiorWidth}")
            print(f"  previous output data byte size: {len(wrappedData)}")
            #print(f"  widthInterior: {widthInterior}, paddingWidthLeft: {paddingWidthLeft}, widthLeftAndInterior: {widthLeftAndInterior}")

            wrappedData.extend([0 for _ in range(clampedPaddingWidthLeft)])
            rowStartOffset = previousWrappedStart + rowHeaderByteSize
            wrappedData += fileData[rowStartOffset : rowStartOffset + clampedInteriorWidth]
            wrappedData.extend([0 for _ in range(clampedPaddingWidthRight)])

            previousWrappedStart = fileOffset
            fileOffset += rowHeaderByteSize
            rowCount += 1
        else:
            fileOffset += 1
        #endif
    #endfor
#endwith

print(f"Total row count: {rowCount}")

# Write wrapped data file.
if rowCount == 0:
    print("No instances of the split marker were found. Skipping file write.")
else:
    with open(outputDataFilename, 'wb') as f:
        f.write(wrappedData)
    #endwith
#endif

# Write 8bpp image (could use palette mode "P" if the palette format was known).
image = Image.frombytes("L", [maximumWidth, rowCount], wrappedData)
image.save(outputImageFilename)

 

 

 

Edited by piken
Link to comment
Share on other sites

On 12/18/2023 at 8:17 PM, piken said:

Well, I'll defer the palette investigation to you, but here's some wonderfully hacky non-idiomatic Python code (3.9.17) to wrap the input data and output to 8bpp grayscale images:

Thanks! Just one last question, how to use this script? Do I just execute it with cmd or does it need quickbms? 

Edited by Kazuma
Solved
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...