Author Topic: [SEGA, NiGHTS, PRS] Decompressing PRS in python  (Read 4718 times)

Micky

  • *
  • Posts: 300
    • View Profile
[SEGA, NiGHTS, PRS] Decompressing PRS in python
« on: 2011-01-01 13:23:55 »
This is both kind-of on and off topic... I plan to investigate what kind of data I can extract from Panzer Dragoon Saga, and one of the few Sega-internal formats is PRS. I found a closed-source decompressor, and a few open-source (in cpp and c#) that seemed to be unnecessarily complicated. I implemented this routine in python so that other people have a better reference, and I can investigate Saga. I compared the output on NiGHTS with the closed-source decompressor, and the result is identical.
Unfortunately I don't have a model or texture viewer, yet.
(On-topic because the algorithm is based on LZS similar to Xenogears and FF7, so maybe useful if this extension was picked up by square.)
Code: (python) [Select]
# SEGA PRS Decompression (LZS variant)
# Credits:
# based on information/comparing output with
# Nemesis/http://www.romhacking.net/utils/671/
# puyotools/http://code.google.com/p/puyotools/
# fuzziqer software prs/http://www.fuzziqersoftware.com/projects.php

import array

class DecompressPrs:
    def __init__(self, data):
self.ibuf = array.array("B", data)
self.obuf = array.array("B")

self.iofs = 0
self.bit = 0
self.cmd = 0
   
    def getByte(self):
val = self.ibuf[self.iofs]
self.iofs += 1
return val
   
    def getBit(self):
if self.bit == 0:
    self.cmd = self.getByte()
    self.bit = 8
bit = self.cmd & 1
self.cmd >>= 1
self.bit -= 1
return bit

    def decompress(self):
while self.iofs < len(self.ibuf):
    cmd = self.getBit()
    if cmd:
self.obuf.append(self.ibuf[self.iofs])
self.iofs += 1
    else:
t = self.getBit()
if t:
    a = self.getByte()
    b = self.getByte()
   
    offset = ((b << 8) | a) >> 3
    amount = a & 7
    if self.iofs < len(self.ibuf):
if amount == 0:
    amount = self.getByte() + 1
else:
    amount += 2

    start = len(self.obuf) - 0x2000 + offset
else:
    amount = 0
    for j in xrange(2):
amount <<= 1
amount |= self.getBit()
    offset = self.getByte()
    amount += 2
   
    start = len(self.obuf) - 0x100 + offset
for j in xrange(amount):
    if start < 0:
self.obuf.append(0)
    elif start < len(self.obuf):
self.obuf.append(self.obuf[start])
    else:
self.obuf.append(0)
    start += 1

return self.obuf.tostring()
   
def main():
    import glob, os
   
    list = glob.glob("NiGHTS/*.PRS")
    for file in list:
   
f = open(file, "rb")
data = f.read()
f.close()

prs = DecompressPrs(data)
data = prs.decompress()

outfile = os.path.splitext(file)[0] + ".bin"
f = open(outfile, "wb")
f.write(data)
f.close()

if __name__ == "__main__":
    main()
No fancy user interface, you'll have to fix the path to your data in the main routine, or integrate it into your own framework.

Bosola

  • Fire hazard!
  • *
  • Posts: 1752
    • View Profile
    • My YouTube Channel
Re: [SEGA, NiGHTS, PRS] Decompressing PRS in python
« Reply #1 on: 2011-01-01 13:28:00 »
Nice. I can see this being adapted into a Python LZS decompressor.

Also, good luck on working with NiGHTS. I woudn't mind seeing a few mods, actually...

Micky

  • *
  • Posts: 300
    • View Profile
Re: [SEGA, NiGHTS, PRS] Decompressing PRS in python
« Reply #2 on: 2011-01-01 13:39:19 »
Nice. I can see this being adapted into a Python LZS decompressor.
I posted a few already as part of my FF7 or Xenogears viewers...

Mako

  • *
  • Posts: 669
    • View Profile
Re: [SEGA, NiGHTS, PRS] Decompressing PRS in python
« Reply #3 on: 2011-01-01 23:02:41 »
Oh noes no more Xenogears viewer? err Updates? I am greedy thats all don't mind me good work.

Micky

  • *
  • Posts: 300
    • View Profile
Re: [SEGA, NiGHTS, PRS] Decompressing PRS in python
« Reply #4 on: 2011-01-02 11:11:00 »
Oh noes no more Xenogears viewer? err Updates? I am greedy thats all don't mind me good work.
Are you after anything specific? I've seen the q-gears documentation has some more information, but I haven't looked through it, yet.