Skip to content

Instantly share code, notes, and snippets.

@mod-san
Last active February 8, 2019 00:07
Show Gist options
  • Save mod-san/d2e36b62ec2fa32c4ccb3df4ebbda886 to your computer and use it in GitHub Desktop.
Save mod-san/d2e36b62ec2fa32c4ccb3df4ebbda886 to your computer and use it in GitHub Desktop.
Convert SEGA Dreamcast PVR images to PNG ones (of an entire directory, recursively), using Pvr2Png.exe and and the Ruby gem chunky_png. [It has specifically been used to convert Shenmue's PVR files to PNGs.]
require "chunky_png"
class Dir
def self.each_valid_file(path = "**/*")
glob(path) { |f|
File.directory?(f) or f == File.basename(__FILE__) or yield f
}
end
def self.exist_or_make(name)
File.exist?(name) or mkdir(name)
end
end
class File
def self.pvrDissect(f)
string = IO.binread(f); stringSize = string.size; curOffset = 0
while curOffset < stringSize
curOffset = string.index("PVRT", curOffset) or break; curOffset += 4
pvrSize = string[ curOffset...(curOffset + 4) ].unpack("H*")[0].split(/(\d{2})/).reject(&:empty?).reverse.join.hex
pvrSize > stringSize and break
yield string[ (curOffset - 16)...curOffset + 4 ] + string[curOffset + 4, pvrSize]
end
end
end
inputPath = Dir.pwd
outputRootDir = "EXTRACTS"; outputRootPath = "#{inputPath}/#{outputRootDir}"
outputPathPNGs = "#{outputRootPath}/PNGs"
outputPathPVRs = "#{outputRootPath}/PVRs"
# "If the output directories don't exist, then they're created [in the right order, from the outside to the inside]"
[outputRootPath, outputPathPNGs, outputPathPVRs].each { |path| Dir.exist_or_make(path) }
# "Start accessing the whole of the sub-tree."
Dir.glob("#{inputPath}/**/*") { |element| # "For each element found:"
element[0, outputRootPath.size] == outputRootPath and next # "if it's found in the root directory, it's ignored, else"
elementFilename = File.basename(element)
elementPath = File.dirname(element)
outputPathPNG = elementPath.sub(inputPath, outputPathPNGs); cmdOutputPathPNG = outputPathPNG.gsub("/", "\\")
outputPathPVR = elementPath.sub(inputPath, outputPathPVRs); cmdOutputPathPVR = outputPathPVR.gsub("/", "\\")
if File.directory?(element) # "if it's a directory:"
# "if they don't exist already, create in each of the output sub-directories a directory named as the element"
Dir.exist_or_make("#{outputPathPNG}/#{elementFilename}")
Dir.exist_or_make("#{outputPathPVR}/#{elementFilename}")
else # "if it's not a directory (and thus it's a file):"
element == File.basename(__FILE__) and next # "if it's the file __FILE__, it's ignored, else"
id = 0
File.pvrDissect(element) { |data| # "dissect it and"
# "save the result in the matching (based on which directory it's found) directory of PVRs"
suffixPNG = "#{elementFilename}_\##{id}.PNG"; suffixPVR = "#{elementFilename}_\##{id}.PVR"
id += 1; IO.binwrite("#{outputPathPVR}/#{suffixPVR}", data)
%x{Pvr2Png.exe #{ "#{cmdOutputPathPVR}\\#{suffixPVR}" } #{ "#{cmdOutputPathPNG}\\#{suffixPNG}" } }
begin
outputPNG = "#{outputPathPNG}/#{suffixPNG}"
ChunkyPNG::Image.from_file(outputPNG).flip.save(outputPNG)
rescue
next
end
}
end
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment