The rules have been updated, read them now: Rules!

[PS3] The iDOLM@STER One For All Model Format (.par) Help

Post questions about game models here, or help out others!
Post Reply
TheTanStar
ultra-n00b
Posts: 3
Joined: Thu Dec 13, 2018 2:46 am
Been thanked: 3 times

[PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by TheTanStar » Sat Dec 15, 2018 9:42 pm

I've dumped a decrypted DLC character model archive from RAM (the game resources outside of videos and audio are encrypted and compressed) and extracted it with this script:
http://aluigi.org/bms/naruto_paa_bin_arc.bms

Archive: http://www.mediafire.com/file/fft5p0t080tlgl6
Expanded Archive: http://www.mediafire.com/file/d6hpxqtfy78ykmh
Expanded Archive contents:
Image

I managed to extract the .pta file with the same script (it's just a renamed archive with texture files in .gtf format), but I can't make sense of the rest of the files. I assume that the .pmd file contains the model data (it's definitely not a mmd model), but I can't otherwise make sense of the file formatting.

Finally, I have a feeling that the .plt file isn't the correct size due to how I dumped the original archive from RAM.

Any help would be greatly appreciated to help import the archive or its expanded files into a program like noesis to convert it into an usable format.
Last edited by TheTanStar on Fri Jan 18, 2019 7:07 am, edited 1 time in total.

TheTanStar
ultra-n00b
Posts: 3
Joined: Thu Dec 13, 2018 2:46 am
Been thanked: 3 times

Re: [PS3] The iDOLM@STER One For All Model Conversion Help

Post by TheTanStar » Tue Dec 18, 2018 5:14 pm

Added original .par archive to first post

TheTanStar
ultra-n00b
Posts: 3
Joined: Thu Dec 13, 2018 2:46 am
Been thanked: 3 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Hel

Post by TheTanStar » Fri Jan 18, 2019 7:22 am

Updated first post with some clarifications and a preview of the expanded archive.

MarieRose1301
veteran
Posts: 96
Joined: Sat Oct 11, 2014 10:29 pm
Has thanked: 17 times
Been thanked: 5 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by MarieRose1301 » Sun Sep 29, 2019 1:16 pm

triangle strip damage is strong on this one ugh
You do not have the required permissions to view the files attached to this post.

User avatar
shakotay2
MEGAVETERAN
MEGAVETERAN
Posts: 3242
Joined: Fri Apr 20, 2012 9:24 am
Location: Nexus, searching for Jim Kirk
Has thanked: 889 times
Been thanked: 1782 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by shakotay2 » Sun Sep 29, 2019 3:45 pm

MarieRose1301 wrote:
Sun Sep 29, 2019 1:16 pm
triangle strip damage is strong on this one ugh
could be a matter of how the meshes are devided up into submeshes, i.e. where the triangle strips (re-) start.
example from chr_body_rank_104_a_slender.pmd.
.
uvs_test.png
You do not have the required permissions to view the files attached to this post.
Bigchillghost, Reverse Engineering a Game Model: viewtopic.php?f=29&t=17889
extracting simple models: viewtopic.php?f=29&t=10894
Make_H2O-ForzaHor3-jm9.zip
"You quoted the whole thing, what a mess."

MarieRose1301
veteran
Posts: 96
Joined: Sat Oct 11, 2014 10:29 pm
Has thanked: 17 times
Been thanked: 5 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by MarieRose1301 » Sun Sep 29, 2019 4:43 pm

shakotay2 wrote:
Sun Sep 29, 2019 3:45 pm
MarieRose1301 wrote:
Sun Sep 29, 2019 1:16 pm
triangle strip damage is strong on this one ugh
could be a matter of how the meshes are devided up into submeshes, i.e. where the triangle strips (re-) start.
example from chr_body_rank_104_a_slender.pmd.
.
uvs_test.png
Yeah, I've noticed that, this particular model has many unconnected faces which my guess is are still triangle strips with just one triangle in them, right?

mariokart64n
ultra-veteran
ultra-veteran
Posts: 550
Joined: Sun Jun 05, 2005 12:00 pm
Location: Ontario, Canada
Has thanked: 33 times
Been thanked: 186 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by mariokart64n » Thu Apr 30, 2020 12:59 am

A member on the xentax discord named Theos had created a Noesis script and was having troubles importing these meshes with the proper weight ids. I attempted to provide support but came to the conclusion Theo's Noesis script and format specification were incomplete. I spent about 3 days reversing the format and wrote a new Noesis script from scratch.

I found this topic and noticed that you guys probably made the same mistake Theo did, by not reading the mesh table and just reading the buffers straight through. For each submesh there is a header, and an address that goes beyond the buffers to a subheader containing the mesh details for buffer offsets, material ids, and the bone palette.

here is a noesis script I wrote;
Image

I tried to obtain more samples, but I was unable to unpack any usable files from the game or the DLC, so if anyone has more samples please contact me. Due to the lack of samples I had, I'm sure there may be some issues parsing other files beyond the files I had so if you encounter any issues please inform me.

Code: Select all

# The IdolM@ster One For All
# 
# Author: mariokart64n
# URL: https://forum.xentax.com/viewtopic.php?t=19194
# Date: April 26 2020
#
# Credits
#    Chrrox, i modified his strip reading function from an old maxscript
#    Theo, for providing samples and overview for file structure
#

showConsole = False    # shows console, leave off by default
NOEPY_HEADER = 0x504D4401   # PMD Magic

from inc_noesis import *
import math    # needed for sqrt function, used to normalize vertex normals


class pmdblock:
	pos = 0
	endpos = 0
	datapos = 0
	type = 0
	unk001 = 0
	unk002 = 0
	addr = 0
	size = 0
	count = 0
	unk003 = 0
	unk004 = 0
	unk005 = 0
	addrs = []
	sizes = []

	def read_block(self, f=NoeBitStream()):
		self.pos = f.tell()
		self.type = f.readUInt()
		self.unk001 = f.readUShort()
		self.unk002 = f.readUShort()
		self.addr = f.readUInt()
		self.size = f.readUInt()
		self.endpos = self.pos + self.addr
		if self.size > 0 and self.addr == 0x20:
			self.count = f.readUInt()
			self.unk003 = f.readUInt()
			self.unk004 = f.readUInt()
			self.unk005 = f.readUInt()
			if self.count > 0:
				self.addrs = [int] * self.count
				self.sizes = [int] * self.count
			f.seek(self.endpos, NOESEEK_ABS)
			i = 0
			for i in range(0, self.count):
				self.addrs[i] = f.readUInt()
			for i in range(0, self.count):
				self.sizes[i] = f.readUInt()
			self.datapos = self.endpos + (self.count * 8)
			self.endpos = self.datapos + self.size

class pmdPTR_entry:
	pos = 0
	index = 0
	size = 0
	unk006 = 0
	name = ""
	parent_id = 0  # parent
	child_id = 0  # child (bone connection)
	unk009 = 0
	unk010 = 0
	unk011 = []
	matrix = []
	unk012 = 0
	unk013 = 0
	unk014 = 0
	unk015 = 0
	unk016 = 0

	def __init__(self):
		self.unk011 = [float] * 9
		self.matrix = [float] * 16

	def read_ptr_entry(self, f=NoeBitStream()):
		self.pos = f.tell()
		self.index = f.readUInt()
		self.size = f.readUInt()
		self.unk006 = f.readUInt()
		self.name = f.readBytes(32).decode("UTF-8").rstrip("\0")
		self.parent_id = f.readInt()
		self.child_id = f.readInt()
		self.unk009 = f.readUInt()
		self.unk010 = f.readUInt()
		self.unk011 = [
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()
		]
		self.matrix = [
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat(),
			f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()
		]
		self.unk012 = f.readUInt()
		self.unk013 = f.readUInt()
		self.unk014 = f.readUInt()
		self.unk015 = f.readUInt()
		f.seek(self.pos + self.size, NOESEEK_ABS)

class pmdPME_matInfo:
	index = 0
	face_pos = 0
	face_count = 0
	unk080 = 0
	vert_pos = 0
	vert_count = 0

	def read_pmdPME_matInfo(self, f=NoeBitStream()):
		self.index = f.readUInt()
		self.face_pos = f.readUInt()
		self.face_count = f.readUInt()
		self.unk080 = f.readUInt()
		self.vert_pos = f.readUInt()
		self.vert_count = f.readUInt()

class pmdPME_entry:
	pos = 0
	data_pos = 0
	index = 0
	size = 0
	unk006 = 0
	name = ""
	unk020 = 0  # 0
	unk021 = 0  # parent bone
	unk022 = 0  # 0
	unk023 = 0  # -1
	unk024 = 0  # 0
	unk025 = 0  # ?? addr
	vert_count = 0
	unk026 = 0  # always 0?
	vert_buffer_size = 0
	face_count = 0
	face_max_vert_index = 0
	face_buffer_size = 0
	matid_count = 0  # num of materials
	matid = []
	matid_addr = 0
	matid_size = 0
	boneid = []
	boneid_count = 0
	boneid_addr = 0
	boneid_size = 0
	unk037 = 0
	unk038 = 0
	unk039 = 0
	unk040 = 0
	unk041 = 0
	unk042 = 0
	unk043 = 0
	bmin = []
	bmax = []
	unk046 = 0  # 1
	parent_id_addr = 0
	parent_id = 0
	unk048 = 0  # 4
	unk049 = 0  # 0
	unk050 = 0  # 1
	unk051 = 0  # 2
	unk052 = 0  # 1
	end_addr = 0
	unk054 = 0  # 0?
	unk055 = 0  # 0?
	unk056 = 0  # 0?
	unk057 = 0  # float?
	unk058 = 0  # float?
	unk059 = 0  # float?
	unk060 = 0  # float?
	unk061 = 0  # float?
	unk062 = 0  # float?
	unk063 = 0
	unk064 = 0
	unk065 = 0
	unk066 = 0
	unk067 = 0
	unk068 = 0
	unk069 = 0
	unk070 = 0
	unk071 = 0
	unk072 = 0
	unk073 = 0
	unk074 = 0
	unk075 = 0
	unk076 = 0
	unk077 = 0
	unk078 = 0
	unk079 = 0
	unk080 = 0

	def __init__(self):
		self.bmin = [float] * 4
		self.bmax = [float] * 4

	def read_pme_entry(self, f=NoeBitStream()):
		i = 1
		self.pos = f.tell()
		self.index = f.readUInt()
		self.size = f.readUInt()
		self.unk006 = f.readUInt()
		self.name = f.readBytes(32).decode("UTF-8").rstrip("\0")
		self.unk020 = f.readUInt()
		self.unk021 = f.readUInt()
		self.unk022 = f.readUInt()
		self.unk023 = f.readUInt()
		self.unk024 = f.readUInt()
		self.unk025 = f.readUInt()
		self.vert_count = f.readUInt()
		self.unk026 = f.readUInt()
		self.vert_buffer_size = f.readUInt()
		self.face_count = f.readUInt()
		self.face_max_vert_index = f.readUInt()
		self.face_buffer_size = f.readUInt()
		self.matid_count = f.readUInt()
		self.matid_addr = f.readUInt()
		self.matid_size = f.readUInt()
		self.boneid_count = f.readUInt()
		self.boneid_addr = f.readUInt()
		self.boneid_size = f.readUInt()
		self.unk037 = f.readUInt()
		self.unk038 = f.readUInt()
		self.unk039 = f.readUInt()
		self.unk040 = f.readUInt()
		self.unk041 = f.readUInt()
		self.unk042 = f.readUInt()
		self.unk043 = f.readUInt()
		self.bmin = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.bmax = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.unk046 = f.readUInt()
		self.parent_id_addr = f.readUInt()
		self.unk048 = f.readUInt()
		self.unk049 = f.readUInt()
		self.unk050 = f.readUInt()
		self.unk051 = f.readUInt()
		self.unk052 = f.readUInt()
		self.end_addr = f.readUInt()
		self.unk054 = f.readUInt()
		self.unk055 = f.readUInt()
		self.unk056 = f.readUInt()
		self.unk057 = f.readUInt()
		self.unk058 = f.readUInt()
		self.unk059 = f.readUInt()
		self.unk060 = f.readUInt()
		self.unk061 = f.readUInt()
		self.unk062 = f.readUInt()
		self.unk063 = f.readUInt()
		self.unk064 = f.readUInt()
		self.unk065 = f.readUInt()
		self.unk066 = f.readUInt()
		self.unk067 = f.readUInt()
		self.unk068 = f.readUInt()
		self.unk069 = f.readUInt()
		self.unk070 = f.readUInt()
		self.unk071 = f.readUInt()
		self.unk072 = f.readUInt()
		self.unk073 = f.readUInt()
		self.unk074 = f.readUInt()
		self.unk075 = f.readUInt()
		self.unk076 = f.readUInt()
		self.unk077 = f.readUInt()
		self.unk078 = f.readUInt()
		self.unk079 = f.readUInt()
		self.unk080 = f.readUInt()
		self.data_pos = f.tell()
		if self.matid_count > 0:
			f.seek(self.data_pos + self.matid_addr, NOESEEK_ABS)
			self.matid = [pmdPME_matInfo] * self.matid_count
			for i in range(0, self.matid_count):
				self.matid[i] = pmdPME_matInfo()
				self.matid[i].read_pmdPME_matInfo(f)
		if self.boneid_count > 0:
			f.seek(self.data_pos + self.boneid_addr, NOESEEK_ABS)
			self.boneid = [int] * self.boneid_count
			for i in range(0, self.boneid_count):
				self.boneid[i] = f.readUInt()
		f.seek(self.data_pos + self.parent_id_addr, NOESEEK_ABS)
		self.parent_id = f.readUInt()
		f.seek(self.pos + self.size, NOESEEK_ABS)

class pmdPMA_entry:
	pos = 0
	unk198 = 0
	unk199 = 0
	unk200 = 0
	name = ""
	unk201 = 0
	unk202 = 0
	unk203 = 0
	unk204 = 0
	unk205 = 0
	unk206 = 0
	unk207 = 0
	unk208 = 0
	unk209 = 0
	unk210 = 0
	unk211 = 0
	unk212 = 0
	unk213 = 0
	unk214 = 0
	unk215 = 0
	unk216 = 0
	unk217 = 0
	unk218 = 0
	unk219 = [0, 0, 0, 0]
	unk220 = [0, 0, 0, 0]
	unk221 = [0, 0, 0, 0]
	unk222 = [0, 0, 0, 0]
	unk223 = 0.0
	unk224 = 0.0
	diffuseMap_index = 0
	unk226 = 0  # FFs
	unk227 = 0
	normalMap_index = 0
	unk229 = 0
	unk230 = 0.0
	unk231 = 0  # FF
	unk232 = 0
	shadowMap_index = 0
	unk234 = 0
	unk235 = 0
	unk236 = 0
	unk237 = 0
	unk238 = 0
	unk239 = 0
	unk240 = 0
	unk241 = 0
	reflectionMaskMap_index = 0
	unk243 = 0
	unk244 = 0
	unk245 = 0
	unk246 = [0, 0, 0, 0]
	unk247 = [0, 0, 0, 0]
	def read_pma_entry(self, f=NoeBitStream()):
		self.pos = f.tell()
		self.unk198 = f.readUInt()
		self.unk199 = f.readUInt()
		self.unk200 = f.readUInt()
		self.name = f.readBytes(32).decode("UTF-8").rstrip("\0")
		self.unk201 = f.readUInt()
		self.unk202 = f.readUInt()
		self.unk203 = f.readUInt()
		self.unk204 = f.readUInt()
		self.unk205 = f.readUInt()
		self.unk206 = f.readUInt()
		self.unk207 = f.readUInt()
		self.unk208 = f.readUInt()
		self.unk209 = f.readUInt()
		self.unk210 = f.readUInt()
		self.unk211 = f.readUInt()
		self.unk212 = f.readUInt()
		self.unk213 = f.readUInt()
		self.unk214 = f.readUInt()
		self.unk215 = f.readUInt()
		self.unk216 = f.readUInt()
		self.unk217 = f.readUInt()
		self.unk218 = f.readUInt()
		self.unk219 = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.unk220 = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.unk221 = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.unk222 = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.unk223 = f.readFloat()
		self.unk224 = f.readFloat()
		self.diffuseMap_index = f.readUInt()
		self.unk226 = f.readUInt()
		self.unk227 = f.readUInt()
		self.normalMap_index = f.readUInt()
		self.unk229 = f.readUInt()
		self.unk230 = f.readFloat()
		self.unk231 = f.readUInt()
		self.unk232 = f.readUInt()
		self.shadowMap_index = f.readUInt()
		self.unk234 = f.readUInt()
		self.unk235 = f.readUInt()
		self.unk236 = f.readUInt()
		self.unk237 = f.readUInt()
		self.unk238 = f.readUInt()
		self.unk239 = f.readUInt()
		self.unk240 = f.readUInt()
		self.unk241 = f.readUInt()
		self.reflectionMaskMap_index = f.readUInt()
		self.unk243 = f.readUInt()
		self.unk244 = f.readUInt()
		self.unk245 = f.readUInt()
		self.unk246 = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
		self.unk247 = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]

class pmdPAR_entry:
	pos = 0
	name = ""
	version = 0
	image_size = 0
	image_count = 0
	unk103 = 0
	image_addr = 0
	image_size2 = 0
	format = 0
	unk107 = 0
	unk108 = 0
	unk109 = 0
	unk110 = 0
	width = 0
	height = 0
	depth = 0
	unk114 = 0
	unk115 = 0
	unk116 = 0
	unk117 = 0
	unk118 = 0
	unk119 = 0
	unk120 = 0
	unk121 = 0
	unk122 = 0
	unk123 = 0
	unk124 = 0
	unk125 = 0
	unk126 = 0
	unk127 = 0
	unk128 = 0
	unk129 = 0
	unk130 = 0
	unk131 = 0
	unk132 = 0
	unk133 = 0
	unk134 = 0
	unk135 = 0
	unk136 = 0
	def read_par_entry(self, f = NoeBitStream()):
		self.pos = f.tell()
		self.version = f.readUInt()
		self.image_size = f.readUInt()
		self.image_count = f.readUInt()
		self.unk103 = f.readUInt()
		self.image_addr = f.readUInt()
		self.image_size2 = f.readUInt()
		self.format = f.readUByte()
		self.unk107 = f.readUByte()
		self.unk108 = f.readUByte()
		self.unk109 = f.readUByte()
		self.unk110 = f.readUInt()
		self.width = f.readUShort()
		self.height = f.readUShort()
		self.depth = f.readUShort()
		self.unk114 = f.readUShort()
		self.unk115 = f.readUInt()
		self.unk116 = f.readUInt()
		self.unk117 = f.readUInt()
		self.unk118 = f.readUInt()
		self.unk119 = f.readUInt()
		self.unk120 = f.readUInt()
		self.unk121 = f.readUInt()
		self.unk122 = f.readUInt()
		self.unk123 = f.readUInt()
		self.unk124 = f.readUInt()
		self.unk125 = f.readUInt()
		self.unk126 = f.readUInt()
		self.unk127 = f.readUInt()
		self.unk128 = f.readUInt()
		self.unk129 = f.readUInt()
		self.unk130 = f.readUInt()
		self.unk131 = f.readUInt()
		self.unk132 = f.readUInt()
		self.unk133 = f.readUInt()
		self.unk134 = f.readUInt()
		self.unk135 = f.readUInt()
		self.unk136 = f.readUInt()

class ptafile:
	par = []
	def create_image_list(self, f = NoeBitStream()):
		texList = []
		imgData = []
		i = 0
		pos = f.tell()
		filetype = f.readUInt()
		version = f.readUInt()
		count = f.readUInt()
		unk299 = f.readUInt()
		self.par = [pmdPAR_entry] * count
		i = 0
		for i in range(0, count):
			f.seek(pos + 0x10 + (i * 4), NOESEEK_ABS)
			self.par[i] = pmdPAR_entry()
			f.seek(pos + f.readUInt(), NOESEEK_ABS)
			self.par[i].read_par_entry(f)
			f.seek(pos + 0x10 + (count * 4) + (i * 0x80), NOESEEK_ABS)
			self.par[i].name = f.readBytes(128).decode("UTF-8").rstrip("\0")
		if count > 0:
			texList = [NoeTexture] * count
		for i in range(0, count):
			#print(str(i) + " " + self.par[i].name)
			f.seek(self.par[i].pos + self.par[i].image_addr, NOESEEK_ABS)
			imgData = f.readBytes(self.par[i].image_size)
			if self.par[i].format == 0x85:
				texList[i] = NoeTexture(self.par[i].name, self.par[i].width, self.par[i].height, imgData, noesis.NOESISTEX_RGBA32)
			elif self.par[i].format == 0x86:
				texList[i] = NoeTexture(self.par[i].name, self.par[i].width, self.par[i].height, imgData, noesis.NOESISTEX_DXT1)
			elif self.par[i].format == 0x87:
				texList[i] = NoeTexture(self.par[i].name, self.par[i].width, self.par[i].height, imgData, noesis.NOESISTEX_DXT3)
			elif self.par[i].format == 0x88:
				texList[i] = NoeTexture(self.par[i].name, self.par[i].width, self.par[i].height, imgData, noesis.NOESISTEX_DXT5)
			else:
				print(self.par[i].format)
				texList[i] = NoeTexture(self.par[i].name, self.par[i].width, self.par[i].height, imgData, noesis.NOESISTEX_UNKNOWN)
		return texList

class pmdfile:
	ptr = []
	pme = []
	pma = []
	def read_pmd(self, f=NoeBitStream(), fileSize=0):
		f.seek(0, NOESEEK_ABS)
		chunk = pmdblock()
		while f.tell() < fileSize:
			chunk.read_block(f)
			if chunk.type == 0x50545201 and chunk.count > 0:  # PTR (Skeleton)
				self.ptr = [pmdPTR_entry] * chunk.count
				i = 0
				for i in range(0, chunk.count):
					f.seek(chunk.datapos + chunk.addrs[i], NOESEEK_ABS)
					self.ptr[i] = pmdPTR_entry()
					self.ptr[i].read_ptr_entry(f)
				# print(self.ptr[i].matrix[3])
			elif chunk.type == 0x504D4501 and chunk.count > 0:  # PME
				self.pme = [pmdPME_entry] * chunk.count
				i = 0
				for i in range(0, chunk.count):
					f.seek(chunk.datapos + chunk.addrs[i], NOESEEK_ABS)
					self.pme[i] = pmdPME_entry()
					self.pme[i].read_pme_entry(f)
			elif chunk.type == 0x504D4101 and chunk.count > 0:  # PMA
				self.pma = [pmdPMA_entry] * chunk.count
				i = 0
				for i in range(0, chunk.count):
					f.seek(chunk.datapos + chunk.addrs[i], NOESEEK_ABS)
					self.pma[i] = pmdPMA_entry()
					self.pma[i].read_pma_entry(f)
			f.seek(chunk.endpos, NOESEEK_ABS)

	def create_bone_list(self):
		BoneList = []
		BoneMatrix43 = NoeMat43()
		BoneMatrix44 = NoeMat44()
		BoneName = ""
		ParentName = ""
		i = 0
		NumBones = len(self.ptr)
		if NumBones > 0:
			BoneList = [NoeBone] * NumBones
			for i in range(0, NumBones):
				BoneMatrix44 = NoeMat44((
					NoeVec4(
						(self.ptr[i].matrix[0], self.ptr[i].matrix[1], self.ptr[i].matrix[2], self.ptr[i].matrix[3])),
					NoeVec4(
						(self.ptr[i].matrix[4], self.ptr[i].matrix[5], self.ptr[i].matrix[6], self.ptr[i].matrix[7])),
					NoeVec4(
						(self.ptr[i].matrix[8], self.ptr[i].matrix[9], self.ptr[i].matrix[10], self.ptr[i].matrix[11])),
					NoeVec4((self.ptr[i].matrix[12], self.ptr[i].matrix[13], self.ptr[i].matrix[14],
							 self.ptr[i].matrix[15]))
				))

				BoneMatrix43 = BoneMatrix44.toMat43()
				BoneMatrix43 = BoneMatrix43.inverse()

				BoneName = "bone%03i" % i
				ParentName = "bone%03i" % self.ptr[i].parent_id
				if self.ptr[i].name != "":
					BoneName = self.ptr[i].name
				if self.ptr[i].parent_id > -1 and self.ptr[i].parent_id < NumBones:
					if self.ptr[self.ptr[i].parent_id].name != "":
						ParentName = self.ptr[self.ptr[i].parent_id].name
					BoneList[i] = (NoeBone(i, BoneName, BoneMatrix43, ParentName, self.ptr[i].parent_id))
				else:
					BoneList[i] = (NoeBone(i, self.ptr[i].name, BoneMatrix43, None, -1))
		return BoneList

	def create_mesh_list(self, f = NoeBitStream(), texList = []):
		mshList = []
		matList = []
		posList = []
		nrmList = []
		bWeList = []
		colList = []
		uv0List = []
		uv1List = []
		idxList = []
		vert = [0.0, 0.0, 0.0, 0.0]
		norm = [0.0, 0.0, 0.0, 0.0]
		colr = [1.0, 0.0, 0.0, 0.0]
		boWe = [1.0, 0.0, 0.0, 0.0]
		boId = [0, 0, 0, 0]
		uvw0 = [0.0, 0.0]
		uvw1 = [0.0, 0.0]
		mesh = 0
		element = 0
		mesh_count = 0
		mesh_index = 0
		material_count = 0
		material_name = ""
		texture_name = ""
		texture_count = 0
		vstride = 0
		fstride = 0
		vcount = 0
		vertAdd = 0
		StartDirection = 1
		FaceDirection = 1
		f1 = 1
		f2 = 2
		f3 = 3
		face_term = 0xFFFFFFFF
		mat_index = 0
		v = 0
		x = 0
		div = 0.0


		mesh_count = 0
		for mesh in range(0, len(self.pme)):
			mesh_count = mesh_count + self.pme[mesh].matid_count

		if mesh_count > 0:
			mshList = [NoeMesh] * mesh_count
			matList = [NoeMaterial] * mesh_count

		mesh_index = 0
		material_count = len(self.pma)
		texture_count = len(texList)

		for mesh in range(0, len(self.pme)):
			vstride = int(self.pme[mesh].vert_buffer_size / self.pme[mesh].vert_count)
			fstride = int(self.pme[mesh].face_buffer_size / self.pme[mesh].face_count)
			vcount = 0
			vertAdd = 0
			for element in range(0, self.pme[mesh].matid_count):
				material_name = "mat%03i" % mesh_index
				texture_name = ""
				if self.pme[mesh].matid[element].index < material_count:
					material_name = self.pma[self.pme[mesh].matid[element].index].name
					if self.pma[self.pme[mesh].matid[element].index - self.pma[self.pme[mesh].matid[element].index].unk243].diffuseMap_index < texture_count:
						texture_name = texList[self.pma[self.pme[mesh].matid[element].index - self.pma[self.pme[mesh].matid[element].index].unk243].diffuseMap_index].name
				#print("MaterialName " + str(material_name))
				#print("TextureName " + str(texture_name))
				matList[mesh_index] = NoeMaterial(material_name, texture_name)
				matList[mesh_index].setDefaultBlend(False)
				if texture_name != "":
					if self.pma[self.pme[mesh].matid[element].index].normalMap_index > -1 and self.pma[self.pme[mesh].matid[element].index].normalMap_index < texture_count:
						matList[mesh_index].setNormalTexture(textList[self.pma[self.pme[mesh].matid[element].index].normalMap_index].name)
					if self.pma[self.pme[mesh].matid[element].index].shadowMap_index > -1 and self.pma[self.pme[mesh].matid[element].index].shadowMap_index < texture_count:
						matList[mesh_index].setOcclTexture(textList[self.pma[self.pme[mesh].matid[element].index].shadowMap_index].name)


				vstride = int(self.pme[mesh].vert_buffer_size / self.pme[mesh].vert_count)
				fstride = int(self.pme[mesh].face_buffer_size / self.pme[mesh].face_count)
				if (element + 1) == self.pme[mesh].matid_count:
					vcount = self.pme[mesh].vert_count - self.pme[mesh].matid[element].vert_pos
				else:
					vcount = self.pme[mesh].matid[element + 1].vert_pos - self.pme[mesh].matid[element].vert_pos
				colList = []
				posList = []
				nrmList = []
				bWeList = []
				uv0List = []
				uv1List = []
				idxList = []
				if vcount > 0:
					colList = [NoeVec4] * vcount
					posList = [NoeVec3] * vcount
					nrmList = [NoeVec3] * vcount
					bWeList = [NoeVertWeight] * vcount
					uv0List = [NoeVec3] * vcount
					uv1List = [NoeVec3] * vcount
				for v in range(0, vcount):
					f.seek(self.pme[mesh].data_pos + (vertAdd * vstride) + (v * vstride), NOESEEK_ABS)
					vert = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
					norm = [float(f.readShort() / 32767.0), float(f.readShort() / 32767.0),
							float(f.readShort() / 32767.0), float(f.readShort() / 32767.0)]
					div = math.sqrt((norm[0] * norm[0]) + (norm[1] * norm[1]) + (norm[2] * norm[2]))
					norm = [norm[0] / div, norm[1] / div, norm[2] / div, norm[3]]
					uvw0 = [f.readHalfFloat(), f.readHalfFloat()]
					uvw1 = [0.0, 0.0]
					if vstride > 72:
						uvw1 = [f.readHalfFloat(), f.readHalfFloat()]
					colr = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
					boWe = [1.0, 0.0, 0.0, 0.0]
					boId = [0, 0, 0, 0]
					if vstride > 52:
						boWe = [f.readFloat(), f.readFloat(), f.readFloat(), f.readFloat()]
						boId = [f.readUByte(), f.readUByte(), f.readUByte(), f.readUByte()]
					f.seek(8, NOESEEK_REL)  # Tangents?
					posList[v] = NoeVec3((vert[0] * vert[3], vert[1] * vert[3], vert[2] * vert[3]))
					nrmList[v] = NoeVec3((norm[0], norm[1], norm[2]))
					uv0List[v] = NoeVec3((uvw0[0], uvw0[1], 0.0))
					uv1List[v] = NoeVec3((uvw1[0], uvw1[1], 0.0))
					bWeList[v] = NoeVertWeight(boId, boWe)
					colList[v] = NoeVec4((colr[0], colr[1], colr[2], 1.0))

				f.seek(self.pme[mesh].data_pos + self.pme[mesh].vert_buffer_size + (self.pme[mesh].matid[element].face_pos * fstride), NOESEEK_ABS)
				#  This Algo for reading the face strip was taken from
				#  one of chrrox's old maxscripts and adapted here

				StartDirection = -1
				FaceDirection = 1
				x = 0
				f1 = 1
				f2 = 2
				f3 = 3
				face_term = 0xFFFFFFFF
				StartDirection = -1
				if fstride == 1:
					f1 = f.readUByte()
					f2 = f.readUByte()
					face_term = 0xFF
				elif fstride == 2:
					f1 = f.readUShort()
					f2 = f.readUShort()
					face_term = 0xFFFF
				FaceDirection = StartDirection
				while True:
					if f.tell() >= (self.pme[mesh].data_pos + self.pme[mesh].vert_buffer_size + ((self.pme[mesh].matid[element].face_pos + self.pme[mesh].matid[element].face_count) * fstride)):
						break
					if fstride == 1:
						f3 = f.readUByte()
					elif fstride == 2:
						f3 = f.readUShort()
					if f3 == face_term:
						if fstride == 1:
							f1 = f.readUByte()
							f2 = f.readUByte()
						elif fstride == 2:
							f1 = f.readUShort()
							f2 = f.readUShort()
						FaceDirection = StartDirection
					else:
						FaceDirection *= -1
						if f1 != f2 and f2 != f3 and f3 != f1:
							if FaceDirection > 0:
								idxList.append(f1 - vertAdd)
								idxList.append(f3 - vertAdd)
								idxList.append(f2 - vertAdd)
							else:
								idxList.append(f1 - vertAdd)
								idxList.append(f2 - vertAdd)
								idxList.append(f3 - vertAdd)
						f1 = f2
						f2 = f3
				vertAdd += vcount
				mshList[mesh_index] = NoeMesh(idxList, posList, "mesh%03i" % mesh_index)
				if self.pme[mesh].name != "":
					mshList[mesh_index].setMaterial(self.pme[mesh].name)
				mshList[mesh_index].setMaterial(material_name)
				mshList[mesh_index].setIndices(idxList)
				mshList[mesh_index].setPositions(posList)
				mshList[mesh_index].setNormals(nrmList)
				mshList[mesh_index].setUVs(uv0List, 0)
				mshList[mesh_index].setUVs(uv1List, 1)
				#mshList[mesh_index].setColors(colList)
				mshList[mesh_index].setWeights(bWeList)
				if self.pme[mesh].boneid_count > 0:
					mshList[mesh_index].setBoneMap(self.pme[mesh].boneid)
				else:
					mshList[mesh_index].setBoneMap([self.pme[mesh].parent_id])
				mesh_index = mesh_index + 1
		return mshList, matList

def noepyLoadModel(data, mdlList):
	f = NoeBitStream(data, NOE_BIGENDIAN)
	if f.readUInt() != NOEPY_HEADER:
		return 0

	pmd = pmdfile()
	pta = ptafile()
	TexList = []
	MatList = []

	#  Check if there is a texture library
	filename = rapi.getInputName()
	filename = filename[:-3] + {'PMD': 'PTA'}[filename[-3:].upper()]
	if rapi.checkFileExists(filename):
		t = NoeBitStream(rapi.loadIntoByteArray(filename), NOE_BIGENDIAN)
		TexList = pta.create_image_list(t)
	pmd.read_pmd(f, len(data))
	BoneList = pmd.create_bone_list()
	MeshList, MatList = pmd.create_mesh_list(f, TexList)
	mdl = NoeModel()
	if showConsole: print("NumBones: " + str(len(BoneList)))
	mdl.setBones(BoneList)
	mdl.setMeshes(MeshList)
	mdl.setModelMaterials(NoeModelMaterials(TexList, MatList))
	mdlList.append(mdl)
	return 1

def noepyCheckType(data):
	if len(data) < 8:
		return 0
	f = NoeBitStream(data, NOE_BIGENDIAN)
	if f.readUInt() != NOEPY_HEADER:
		return 0
	return 1

def registerNoesisTypes():
	if showConsole == True:
		noesis.logPopup()
		for i in range(0, 30): print("\n")
	handle = noesis.register("The Idolm@aster: One For All (PS3)", ".pmd")
	noesis.setHandlerTypeCheck(handle, noepyCheckType)
	noesis.setHandlerLoadModel(handle, noepyLoadModel)
	return 1

Maxscript and other finished work I've done can be found on my DeviantArt account

User avatar
shakotay2
MEGAVETERAN
MEGAVETERAN
Posts: 3242
Joined: Fri Apr 20, 2012 9:24 am
Location: Nexus, searching for Jim Kirk
Has thanked: 889 times
Been thanked: 1782 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by shakotay2 » Thu Apr 30, 2020 8:13 am

mariokart64n wrote:
Thu Apr 30, 2020 12:59 am
I attempted to provide support but came to the conclusion Theo's Noesis script and format specification were incomplete.[...]

I found this topic and noticed that you guys probably made the same mistake Theo did, by not reading the mesh table and just reading the buffers straight through.
There is a big difference between "making a mistake" and just being "uncomplete". :eek:

(There's a whole bunch of "unknowns" in your script. Me, I wouldn't say this is a mistake, would I? :) )

But if you think a "lack of knowledge" is a mistake, generally, I'd agree.:D
Bigchillghost, Reverse Engineering a Game Model: viewtopic.php?f=29&t=17889
extracting simple models: viewtopic.php?f=29&t=10894
Make_H2O-ForzaHor3-jm9.zip
"You quoted the whole thing, what a mess."

mariokart64n
ultra-veteran
ultra-veteran
Posts: 550
Joined: Sun Jun 05, 2005 12:00 pm
Location: Ontario, Canada
Has thanked: 33 times
Been thanked: 186 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by mariokart64n » Thu Apr 30, 2020 4:38 pm

I don't understand your criticism of having variables named unknown? All the block structures are completely mapped to each respective class making inspecting each variable possible. The fact is; you guys should have been more diligent and read the mesh table properly, your incompetence was not your lack of knowledge but rather your laziness. :D
Maxscript and other finished work I've done can be found on my DeviantArt account

MarieRose1301
veteran
Posts: 96
Joined: Sat Oct 11, 2014 10:29 pm
Has thanked: 17 times
Been thanked: 5 times

Re: [PS3] The iDOLM@STER One For All Model Format (.par) Help

Post by MarieRose1301 » Sun May 10, 2020 2:37 pm

Apparently the _SDW ambient occlusion maps sometimes may include object space normal maps in them, for outfits like both Leon's costumes and probably Miki's Nostalgia one.
Image

Post Reply