public void Parse(BinaryReader buffer) { ptrRoomSection = buffer.ReadUInt32(); lenRoomSection = buffer.ReadUInt32(); ptrClearedSection = buffer.ReadUInt32(); lenClearedSection = buffer.ReadUInt32(); ptrScriptSection = buffer.ReadUInt32(); lenScriptSection = buffer.ReadUInt32(); ptrDoorSection = buffer.ReadUInt32(); lenDoorSection = buffer.ReadUInt32(); ptrEnemySection = buffer.ReadUInt32(); lenEnemySection = buffer.ReadUInt32(); ptrTreasureSection = buffer.ReadUInt32(); lenTreasureSection = buffer.ReadUInt32(); // Room sub sections lenGeometrySection = buffer.ReadUInt32(); lenCollisionSection = buffer.ReadUInt32(); lenSubSection03 = buffer.ReadUInt32(); lenRoomDoorSection = buffer.ReadUInt32(); lenLightingSection = buffer.ReadUInt32(); lenSubSection06 = buffer.ReadUInt32(); lenSubSection07 = buffer.ReadUInt32(); lenSubSection08 = buffer.ReadUInt32(); lenSubSection09 = buffer.ReadUInt32(); lenSubSection0A = buffer.ReadUInt32(); lenSubSection0B = buffer.ReadUInt32(); lenTextureEffectsSection = buffer.ReadUInt32(); lenSubSection0D = buffer.ReadUInt32(); lenSubSection0E = buffer.ReadUInt32(); lenSubSection0F = buffer.ReadUInt32(); lenSubSection10 = buffer.ReadUInt32(); lenSubSection11 = buffer.ReadUInt32(); lenSubSection12 = buffer.ReadUInt32(); lenSubSection13 = buffer.ReadUInt32(); lenAKAOSubSection = buffer.ReadUInt32(); lenSubSection15 = buffer.ReadUInt32(); lenSubSection16 = buffer.ReadUInt32(); lenSubSection17 = buffer.ReadUInt32(); lenSubSection18 = buffer.ReadUInt32(); if (UseDebug) { Debug.Log("MPD parse : " + filePath); Debug.Log("ptrRoomSection :" + ptrRoomSection + " lenRoomSection : " + lenRoomSection); Debug.Log("ptrClearedSection :" + ptrClearedSection + " lenClearedSection : " + lenClearedSection); Debug.Log("ptrScriptSection :" + ptrScriptSection + " lenScriptSection : " + lenScriptSection); Debug.Log("ptrDoorSection :" + ptrDoorSection + " lenDoorSection : " + lenDoorSection); Debug.Log("ptrEnemySection :" + ptrEnemySection + " lenEnemySection : " + lenEnemySection); Debug.Log("ptrTreasureSection :" + ptrTreasureSection + " lenTreasureSection : " + lenTreasureSection); Debug.Log("lenGeometrySection :" + lenGeometrySection); Debug.Log("lenCollisionSection :" + lenCollisionSection); Debug.Log("lenSubSection03 :" + lenSubSection03); Debug.Log("lenRoomDoorSection :" + lenRoomDoorSection); Debug.Log("lenLightingSection :" + lenLightingSection); Debug.Log("lenSubSection06 :" + lenSubSection06); Debug.Log("lenSubSection07 :" + lenSubSection07); Debug.Log("lenSubSection08 :" + lenSubSection08); Debug.Log("lenSubSection09 :" + lenSubSection09); Debug.Log("lenSubSection0A :" + lenSubSection0A); Debug.Log("lenSubSection0B :" + lenSubSection0B); Debug.Log("lenTextureEffectsSection :" + lenTextureEffectsSection); Debug.Log("lenSubSection0D :" + lenSubSection0D); Debug.Log("lenSubSection0E :" + lenSubSection0E); Debug.Log("lenSubSection0F :" + lenSubSection0F); Debug.Log("lenSubSection10 :" + lenSubSection10); Debug.Log("lenSubSection11 :" + lenSubSection11); Debug.Log("lenSubSection12 :" + lenSubSection12); Debug.Log("lenSubSection13 :" + lenSubSection13); Debug.Log("lenAKAOSubSection :" + lenAKAOSubSection); Debug.Log("lenSubSection15 :" + lenSubSection15); Debug.Log("lenSubSection16 :" + lenSubSection16); Debug.Log("lenSubSection17 :" + lenSubSection17); Debug.Log("lenSubSection18 :" + lenSubSection18); } // ROOM section if (UseDebug) { Debug.Log("ROOM section : " + buffer.BaseStream.Position); } // Geometry if (lenRoomSection > 4) { if (lenGeometrySection > 0) { numGroups = buffer.ReadUInt32(); if (UseDebug) { Debug.Log("numGroups : " + numGroups); } groups = new MPDGroup[numGroups]; for (uint i = 0; i < numGroups; i++) { groups[i] = new MPDGroup(); groups[i].header = buffer.ReadBytes(64); if ((groups[i].header[1] & 0x08) > 0) { groups[i].scale = 1; } } for (uint i = 0; i < numGroups; i++) { uint numTriangles = buffer.ReadUInt32(); uint numQuads = buffer.ReadUInt32(); for (uint j = 0; j < numTriangles; j++) { MPDFace face = new MPDFace(groups[i], false); face.feed(buffer); MPDMesh m = groups[i].getMesh(face.textureId, face.clutId); m.addFace(face); } for (uint j = 0; j < numQuads; j++) { MPDFace face = new MPDFace(groups[i], true); face.feed(buffer); MPDMesh m = groups[i].getMesh(face.textureId, face.clutId); m.addFace(face); } } } // collision if (lenCollisionSection > 0) { long collisionPtr = buffer.BaseStream.Position; uint TyleWidth = buffer.ReadUInt16(); uint TyleHeight = buffer.ReadUInt16(); if (UseDebug) { Debug.Log("TyleWidth : " + TyleWidth + " TyleHeight : " + TyleHeight); } uint unk1 = buffer.ReadUInt16(); uint unk2 = buffer.ReadUInt16(); uint[] FloorHeight = new uint[TyleWidth * TyleHeight]; uint[] CeilingHeight = new uint[TyleWidth * TyleHeight]; uint[] Incline = new uint[TyleWidth * TyleHeight]; //Debug.Log("Collision ptr : " + buffer.BaseStream.Position); for (uint i = 0; i < TyleWidth * TyleHeight; i++) { FloorHeight[i] = buffer.ReadUInt16(); if (UseDebug) { //Debug.Log("FloorHeight[i] : " + FloorHeight[i]); } } for (uint i = 0; i < TyleWidth * TyleHeight; i++) { CeilingHeight[i] = buffer.ReadUInt16(); if (UseDebug) { //Debug.Log("CeilingHeight[i] : " + CeilingHeight[i]); } } for (uint i = 0; i < TyleWidth * TyleHeight / 2; i++) { byte b = buffer.ReadByte(); Incline[i * 2] = (uint)b << 4; Incline[i * 2 + 1] = (uint)b >> 4; } buffer.BaseStream.Position = collisionPtr + lenCollisionSection; } // section 3 ?? // if (lenSubSection03 > 0) { Debug.Log("lenSubSection03 ptr : " + buffer.BaseStream.Position + " lenSubSection03 : " + lenSubSection03); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection03; } // door section if (lenRoomDoorSection > 0) { long doorPtr = buffer.BaseStream.Position; uint numDoors = lenRoomDoorSection / 0x0C; doors = new MPDDoor[numDoors]; for (uint i = 0; i < numDoors; i++) { MPDDoor d = new MPDDoor(); d.destZone = buffer.ReadByte(); d.destRoom = buffer.ReadByte(); d.unkn = buffer.ReadBytes(6); d.idCurrentDoor = buffer.ReadUInt32(); doors[i] = d; if (UseDebug) { Debug.Log("MPDDoor # " + i + " idCurrentDoor : " + d.idCurrentDoor + " destZone : " + d.destZone + " destRoom : " + d.destRoom); Debug.Log(d.unkn[0] + ", " + d.unkn[1] + ", " + d.unkn[2] + ", " + d.unkn[3] + ", " + d.unkn[4] + ", " + d.unkn[5]); } } buffer.BaseStream.Position = doorPtr + lenRoomDoorSection; } // lights section // http://www.psxdev.net/forum/viewtopic.php?f=51&t=3383 if (lenLightingSection > 0) { long lightPtr = buffer.BaseStream.Position; if (UseDebug) { Debug.Log("lights ptr : " + lightPtr + " lenLightingSection : " + lenLightingSection + "(" + (lightPtr + lenLightingSection) + ")"); } Color32 mc = new Color32(buffer.ReadByte(), buffer.ReadByte(), buffer.ReadByte(), buffer.ReadByte()); uint numLights = buffer.ReadUInt32(); if (UseDebug) { Debug.Log("numLights : " + numLights + " mainColor : " + mc.ToString()); } buffer.ReadUInt32(); // padding lights = new List <GameObject>(); string lightsDebug = ""; for (uint i = 0; i < numLights; i++) { string lgtMat = ""; short[] matrix = new short[10]; byte[] hexa = buffer.ReadBytes(20); buffer.BaseStream.Position -= 20; for (uint j = 0; j < 10; j++) { matrix[j] = buffer.ReadInt16(); lgtMat += (matrix[j]) + " | "; } byte[] cols = buffer.ReadBytes(12); Color32 colorX = new Color32(cols[0], cols[1], cols[2], cols[3]); Color32 colorY = new Color32(cols[4], cols[5], cols[6], cols[7]); Color32 colorZ = new Color32(cols[8], cols[9], cols[10], cols[11]); Color32 main = Color.black; if (colorX.r != mc.r && colorX.g != mc.g && colorX.b != mc.b) { main = colorX; } if (colorY.r != mc.r && colorY.g != mc.g && colorY.b != mc.b) { main = colorY; } if (colorZ.r != mc.r && colorZ.g != mc.g && colorZ.b != mc.b) { main = colorZ; } main.a = 255; lightsDebug += string.Concat("Light # ", i, " : ", BitConverter.ToString(hexa), " -> ", lgtMat, " | ", colorX, ", ", colorY, ", ", colorZ, "\r\n"); GameObject lgo = new GameObject("Point Light"); Rect lightRect = new Rect(); lightRect.xMin = -matrix[0] / 100; lightRect.yMin = -matrix[1] / 100; lightRect.xMax = -matrix[2] / 100; lightRect.yMax = -matrix[3] / 100; // i need to find a way to get Y axe lgo.transform.position = new Vector3(lightRect.center.x, 5f, lightRect.center.y); lgo.transform.localScale = Vector3.one; Light l = lgo.AddComponent <Light>(); l.name = "l" + i; l.type = LightType.Point; l.range = Vector2.Distance(lightRect.center, lightRect.min); l.intensity = 2f; l.color = main; l.shadows = LightShadows.Soft; lights.Add(lgo); } if (UseDebug) { Debug.Log(lightsDebug); } if (UseDebug) { Debug.Log("end lights : " + buffer.BaseStream.Position); } buffer.BaseStream.Position = lightPtr + lenLightingSection; } if (lenSubSection06 > 0) { Debug.Log("SubSection06 ptr : " + buffer.BaseStream.Position + " lenSubSection06 : " + lenSubSection06); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection06; } if (lenSubSection07 > 0) { Debug.Log("SubSection07 ptr : " + buffer.BaseStream.Position + " lenSubSection07 : " + lenSubSection07); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection07; } if (lenSubSection08 > 0) { Debug.Log("SubSection08 ptr : " + buffer.BaseStream.Position + " lenSubSection08 : " + lenSubSection08); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection08; } if (lenSubSection09 > 0) { Debug.Log("SubSection09 ptr : " + buffer.BaseStream.Position + " lenSubSection09 : " + lenSubSection09); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection09; } if (lenSubSection0A > 0) { Debug.Log("SubSection0A ptr : " + buffer.BaseStream.Position + " lenSubSection0A : " + lenSubSection0A); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection0A; } if (lenSubSection0B > 0) { Debug.Log("SubSection0B ptr : " + buffer.BaseStream.Position + " lenSubSection0B : " + lenSubSection0B); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection0B; } if (lenTextureEffectsSection > 0) { Debug.Log("TextureEffectsSection ptr : " + buffer.BaseStream.Position + " lenTextureEffectsSection : " + lenTextureEffectsSection); buffer.BaseStream.Position = buffer.BaseStream.Position + lenTextureEffectsSection; } if (lenSubSection0D > 0) { Debug.Log("SubSection0D ptr : " + buffer.BaseStream.Position + " lenSubSection0D : " + lenSubSection0D); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection0D; } if (lenSubSection0E > 0) { Debug.Log("SubSection0E ptr : " + buffer.BaseStream.Position + " lenSubSection0E : " + lenSubSection0E); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection0E; } if (lenSubSection0F > 0) { Debug.Log("SubSection0F ptr : " + buffer.BaseStream.Position + " lenSubSection0F : " + lenSubSection0F); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection0F; } if (lenSubSection10 > 0) { Debug.Log("SubSection10 ptr : " + buffer.BaseStream.Position + " lenSubSection10 : " + lenSubSection10); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection10; } if (lenSubSection11 > 0) { Debug.Log("SubSection11 ptr : " + buffer.BaseStream.Position + " lenSubSection11 : " + lenSubSection11); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection11; } if (lenSubSection12 > 0) { Debug.Log("SubSection12 ptr : " + buffer.BaseStream.Position + " lenSubSection12 : " + lenSubSection12); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection12; } if (lenSubSection13 > 0) { Debug.Log("SubSection13 ptr : " + buffer.BaseStream.Position + " lenSubSection13 : " + lenSubSection13); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection13; } if (lenAKAOSubSection > 0) { long akaoPtr = buffer.BaseStream.Position; if (UseDebug) { Debug.Log("akaoPtr : " + akaoPtr + " lenAKAOSubSection : " + lenAKAOSubSection + "(" + (akaoPtr + lenAKAOSubSection) + ")"); } buffer.ReadUInt32(); // 0200 0000 buffer.ReadUInt32(); // 0000 0000 buffer.ReadUInt32(); // 0C00 0000 AKAO audio = new AKAO(); audio.FileName = FileName; audio.UseDebug = true; audio.Parse(buffer, AKAO.UNKNOWN, akaoPtr + lenAKAOSubSection); buffer.BaseStream.Position = akaoPtr + lenAKAOSubSection; } if (lenSubSection15 > 0) { Debug.Log("SubSection15 ptr : " + buffer.BaseStream.Position + " lenSubSection15 : " + lenSubSection15); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection15; } if (lenSubSection16 > 0) { Debug.Log("SubSection16 ptr : " + buffer.BaseStream.Position + " lenSubSection16 : " + lenSubSection16); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection16; } if (lenSubSection17 > 0) { Debug.Log("SubSection17 ptr : " + buffer.BaseStream.Position + " lenSubSection17 : " + lenSubSection17); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection17; } if (lenSubSection18 > 0) { Debug.Log("SubSection18 ptr : " + buffer.BaseStream.Position + " lenSubSection18 : " + lenSubSection18); buffer.BaseStream.Position = buffer.BaseStream.Position + lenSubSection18; } } else { // No geometry :s _geom = false; } // Cleared section if (buffer.BaseStream.Position != ptrClearedSection) { buffer.BaseStream.Position = ptrClearedSection; } if (UseDebug) { Debug.Log("Cleared section : " + buffer.BaseStream.Position); } // Script section if (buffer.BaseStream.Position != ptrScriptSection) { buffer.BaseStream.Position = ptrScriptSection; } if (UseDebug) { Debug.Log("Script section : " + buffer.BaseStream.Position); } // See Opcode.cs // Door section if (buffer.BaseStream.Position != ptrDoorSection) { buffer.BaseStream.Position = ptrDoorSection; } if (UseDebug) { Debug.Log("Door section : " + buffer.BaseStream.Position); } if (lenDoorSection > 0) { } // Ennemy section if (buffer.BaseStream.Position != ptrEnemySection) { buffer.BaseStream.Position = ptrEnemySection; } if (UseDebug) { Debug.Log("Ennemy section : " + buffer.BaseStream.Position); } // Treasure section if (buffer.BaseStream.Position != ptrTreasureSection) { buffer.BaseStream.Position = ptrTreasureSection; } if (UseDebug) { Debug.Log("Treasure section : " + buffer.BaseStream.Position); } buffer.Close(); }
private SF2 SoundFundry(AKAO sequencer, AKAO[] sampleCollections) { DLS dls = new DLS(); dls.SetName(FileName + ".dls"); SF2 sf2 = new SF2(); sf2.InfoChunk.Bank = "Vagrant Story SoundFont for " + FileName; sf2.InfoChunk.Products = "Vagrant Story"; sf2.InfoChunk.Tools = "https://github.com/korobetski/Vagrant-Story-Unity-Parser"; sf2.InfoChunk.Designer = "Korobetski Sigfrid"; sf2.InfoChunk.Date = DateTime.Now.ToString(); sf2.InfoChunk.Copyright = "Musics & Samples belong to Hitoshi Sakimoto @ Squaresoft"; sf2.InfoChunk.Comment = string.Concat("This SoundFont was generated by reading raw AKAO format from the original game in SOUND folder.\r", "\nNever forget that musics and samples belongs to SquareEnix, don't use them as your own. Sample collections : ", sampleCollections[0].FileName, ", ", sampleCollections[1].FileName); List <AKAOSample> Samples = new List <AKAOSample>(); // MUSIC024.DAT has no instruments Oo, maybe unfinished work, or development test // we use composer program change to load articulations from WAVE000.DAT the only file where start id = 0 if (sequencer.instruments == null) { foreach (uint id in sequencer.composer.progIDs) { AKAOInstrument instrument = new AKAOInstrument(id, AKAOInstrument.InstrumentType.INSTR_MELODIC); instrument.name = "No instrument " + id; instrument.regions = new AKAORegion[1]; AKAORegion defaultRegion = new AKAORegion(); defaultRegion.articulationId = (byte)id; instrument.regions[0] = defaultRegion; sequencer.instruments = new List <AKAOInstrument>(); sequencer.instruments.Add(instrument); } } if (sequencer.instruments != null) { uint i = 0; foreach (AKAOInstrument instrument in sequencer.instruments) { if (composer.progIDs.Contains(instrument.program) || composer.A1Calls.Contains(instrument.program) || instrument.IsDrum()) { uint midiBank = 0x00000000; if (instrument.IsDrum()) { midiBank = DLS.F_INSTRUMENT_DRUMS; sf2.AddPreset(instrument.name, 0, 128); } else { sf2.AddPreset(instrument.name, (ushort)instrument.program, 0); } sf2.AddPresetBag(); sf2.AddPresetGenerator(SF2Generator.ReverbEffectsSend, new SF2GeneratorAmount { UAmount = (ushort)1000 }); sf2.AddPresetGenerator(SF2Generator.Instrument, new SF2GeneratorAmount { UAmount = (ushort)i }); sf2.AddInstrument(instrument.name); i++; if (instrument.regions.Length > 0) { Lins DSLInstrument = new Lins(midiBank, instrument.program, instrument.name); foreach (AKAORegion region in instrument.regions) { AKAOArticulation articulation = null; AKAO coll = sampleCollections[2]; if (region.articulationId >= 0 && region.articulationId < 32) { // trick for MUSIC024.DAT coll = sampleCollections[1]; articulation = coll.articulations[region.articulationId]; } else if (region.articulationId >= 32 && region.articulationId < 64) { coll = sampleCollections[0]; articulation = coll.articulations[region.articulationId - coll.startingArticulationId]; } else if (region.articulationId >= 64 && region.articulationId < 128) { coll = sampleCollections[1]; if (region.articulationId - coll.startingArticulationId < coll.articulations.Length /* && !instrument.a1*/) { articulation = coll.articulations[region.articulationId - coll.startingArticulationId]; } else { // we check in additional collection //Debug.LogWarning(region.articulationId); coll = sampleCollections[2]; articulation = coll.articulations[region.articulationId - coll.startingArticulationId]; } } if (UseDebug) { Debug.Log(string.Concat("Instrument ", i, " ", instrument.name, " | Region articulation " + region.articulationId + " found in ", coll.FileName)); } if (articulation != null) { articulation.BuildADSR(); region.articulation = articulation; AKAOSample sample = coll.samples[articulation.sampleNum]; if (instrument.IsDrum()) { region.unityKey = (uint)articulation.unityKey + region.lowRange - region.relativeKey; // maybe } else { region.unityKey = articulation.unityKey; } short ft = articulation.fineTune; if (ft < 0) { ft += short.MaxValue; } double freq_multiplier = ((ft * 32) + 0x100000) / (double)0x100000; double cents = (short)(1200 * Math.Log(freq_multiplier, 2)); if (articulation.fineTune < 0) { cents -= 1200; } region.fineTune = (short)cents; sample.loopStart = (uint)(articulation.loopPt * 1.75); sample.unityKey = (byte)region.unityKey; if (!Samples.Contains(sample)) { Samples.Add(sample); } int sampleIDX = Samples.IndexOf(sample); // Making DLS Lrgn reg = new Lrgn(region.lowRange, region.hiRange, 0x00, 0x7F); CKwsmp smp = new CKwsmp((ushort)region.unityKey, region.fineTune, region.attenuation, 1); if (articulation.loopPt != 0) { smp.AddLoop(new Loop(1, (uint)(articulation.loopPt * 1.75f), (uint)(sample.size * 1.75f - articulation.loopPt * 1.75f))); } reg.SetSample(smp); CKart2 iart = new CKart2(); iart.AddPan(0x40); iart.AddADSR(articulation.A, articulation.D, articulation.S, articulation.R, articulation.AT, articulation.RT); reg.AddArticulation(iart); reg.SetWaveLinkInfo(0, 0, 1, region.sampleNum); DSLInstrument.AddRegion(reg); // http://linuxmao.org/SoundFont+specification+SF2 sf2.AddInstrumentBag(); sf2.AddInstrumentGenerator(SF2Generator.KeyRange, new SF2GeneratorAmount { LowByte = region.lowRange, HighByte = region.hiRange }); //sf2.AddInstrumentGenerator(SF2Generator.VelRange, new SF2GeneratorAmount { LowByte = region.lowVel, HighByte = region.hiVel }); // not sure sf2.AddInstrumentGenerator(SF2Generator.VelRange, new SF2GeneratorAmount { LowByte = 0, HighByte = 127 }); /* C'est l'atténuation, en centibels, pour laquelle une note est atténuée en dessous de la valeur maximum prévue. * Si = 0, il n'y a aucune atténuation, la note sera jouée au maximum prévu. * Ex : 60 indique que la note sera jouée à 6 dB en-dessous du maximum prévu pour la note. * Max value = 1440 */ sf2.AddInstrumentGenerator(SF2Generator.InitialAttenuation, new SF2GeneratorAmount { UAmount = (ushort)(region.attenuation / 10) }); //sf2.AddInstrumentGenerator(SF2Generator.ReverbEffectsSend, new SF2GeneratorAmount { Amount = 1000 }); sf2.AddInstrumentGenerator(SF2Generator.Pan, new SF2GeneratorAmount { Amount = region.pan }); sf2.AddInstrumentGenerator(SF2Generator.SampleModes, new SF2GeneratorAmount { UAmount = (articulation.loopPt != 0) ? (ushort)1 : (ushort)0 }); sf2.AddInstrumentGenerator(SF2Generator.OverridingRootKey, new SF2GeneratorAmount { UAmount = (ushort)region.unityKey }); sf2.AddInstrumentGenerator(SF2Generator.DelayVolEnv, new SF2GeneratorAmount { Amount = (short)short.MinValue }); /* En timecents absolu, c'est la durée, depuis la fin du délai de l'enveloppe de volume jusqu'au point où la valeur de l'enveloppe de volume atteint son apogée. * Une valeur de 0 indique 1 seconde. Une valeur négative indique un temps inférieur à une seconde, une valeur positive un temps supérieur à une seconde. * Le nombre le plus négatif (-32768) indique conventionnellement une attaque instantanée. * Ex : un temps d'attaque de 10 ms serait 1200log2 (.01) = -7973. * En musique, le logarithme binaire intervient dans la formule permettant de déterminer la valeur en cents d’un intervalle. * Un cent, ou centième de demi-ton au tempérament égal, vaut 1200 fois le logarithme binaire du rapport de fréquence des sons concernés. * 546 * 60 ~= short.MaxValue */ sf2.AddInstrumentGenerator(SF2Generator.AttackVolEnv, new SF2GeneratorAmount { Amount = (short)(1200 * Math.Log(articulation.A, 2)) }); sf2.AddInstrumentGenerator(SF2Generator.HoldVolEnv, new SF2GeneratorAmount { Amount = (short)0 }); /* C'est le temps, en timecents absolus, pour une variation de 100% de la valeur de l'enveloppe du volume pendant la phase de décroissance. * Pour l'enveloppe de volume, la décroissance tend linéairement vers le niveau de maintien, ce qui provoque un changement de dB constant pour chaque unité de temps. * Si le niveau de maintien = -100dB, le temps de décroissance de l'enveloppe de volume = temps de la phase de décroissance. * Une valeur de 0 indique 1 seconde de temps de décroissance pour un niveau zéro. Une valeur négative indique un temps inférieur à une seconde, * une valeur positive un temps supérieur à une seconde. * Ex : un temps de décroissance de 10 msec serait 1200log2 (.01) = -7973.*/ sf2.AddInstrumentGenerator(SF2Generator.DecayVolEnv, new SF2GeneratorAmount { Amount = (short)(1200 * Math.Log(articulation.D, 2)) }); /* C'est le taux de la diminution, exprimé en centibels, pour laquelle l'enveloppe de volume décroît au cours de la phase de décroissance. * Pour l'enveloppe de volume, le niveau d'atténuation du sustain est mieux exprimé en centibels. Une valeur de 0 indique que le niveau est maximum. * Une valeur positive indique une décroissance au niveau correspondant. Les valeurs inférieures à zéro doivent être interprétés comme zéro; * conventionnellement 1000 indique une atténuation complète. * Ex : un niveau de soutien qui correspond à une valeur absolue de 12 dB en dessous du pic serait 120.*/ sf2.AddInstrumentGenerator(SF2Generator.SustainVolEnv, new SF2GeneratorAmount { Amount = (short)(articulation.S) }); /* C'est la durée, en timecents absolu, pour une variation de 100% de la valeur de l'enveloppe du volume pendant la phase de libération (release). * Pour l'enveloppe de volume, la phase de libération tend linéairement vers zéro depuis la niveau en cours, * ce qui provoque un changement en dB constant pour chaque unité de temps. * Si le niveau actuel est maximum, la durée du release de l'enveloppe de volume sera le temps de libération jusqu'à ce que 100 dB d'atténuation soit atteint. * Une valeur de 0 indique 1 seconde de temps de décroissance pour finir complètement. Une valeur négative indique un temps inférieur à une seconde, * une valeur positive un temps de plus d'une seconde. * Ex : un temps de libération de 10 msec serait 1200log2 (.01) = -7973. */ sf2.AddInstrumentGenerator(SF2Generator.ReleaseVolEnv, new SF2GeneratorAmount { Amount = (short)(1200 * Math.Log(articulation.R, 2)) }); /* Décalage de la hauteur, en cents, qui sera appliqué à la note. * Il est additionnel à coarseTune. Une valeur positive indique que le son est reproduit à une hauteur plus élevée, une valeur négative indique une hauteur inférieure. * Ex : une valeur finetune = -5 provoquera un son joué cinq cents plus bas. */ sf2.AddInstrumentGenerator(SF2Generator.FineTune, new SF2GeneratorAmount { Amount = (short)(region.fineTune) }); sf2.AddInstrumentGenerator(SF2Generator.SampleID, new SF2GeneratorAmount { UAmount = (ushort)sampleIDX }); } } dls.AddInstrument(DSLInstrument); } } } } if (Samples.Count > 0) { foreach (AKAOSample AKAOsmp in Samples) { WAV nw = AKAOsmp.ConvertToWAV(); nw.SetName(AKAOsmp.name); nw.Riff = false; dls.AddWave(nw); short[] pcm = AKAOsmp.WAVDatas.ToArray(); sf2.AddSample(pcm, AKAOsmp.name, (AKAOsmp.loopStart > 0), AKAOsmp.loopStart, 44100, AKAOsmp.unityKey, 0); } } if (bDLS) { ToolBox.DirExNorCreate("Assets/Resources/Sounds/DLS/"); dls.WriteFile("Assets/Resources/Sounds/DLS/" + FileName + ".dls"); } if (bSF2) { ToolBox.DirExNorCreate("Assets/Resources/Sounds/SF2/"); sf2.Save("Assets/Resources/Sounds/SF2/" + FileName + ".sf2"); } return(sf2); }
public void Parse(BinaryReader buffer, AKAOType type, long limit = long.MaxValue) { _type = type; if (buffer.BaseStream.Length < 4) { return; } if (_type == AKAOType.UNKNOWN) { // we must try to find the AKAO type byte[] header = buffer.ReadBytes(4); // AKAO if (!CheckHeader(header)) { return; } ushort v1 = buffer.ReadUInt16(); // ID or type ushort v2 = buffer.ReadUInt16(); // File Length or empty byte v3 = buffer.ReadByte(); // Type or empty byte v4 = buffer.ReadByte(); // Type var or empty if (v2 + v3 + v4 == 0) { if (v1 == 0) { _type = AKAOType.SAMPLE; } else { if (buffer.BaseStream.Position == 10) { // v1 is the sample collection ID in this case _type = AKAOType.SOUND; } else { // E075.P have an AKAO PROG without v3 = 0xC8... _type = AKAOType.PROG; } } } else if (v3 == 0xC8) { _type = AKAOType.EFFECT; } else if (v2 > 0 && v2 == buffer.BaseStream.Length) { _type = AKAOType.MUSIC; } buffer.BaseStream.Position -= 10; } switch (_type) { case AKAOType.MUSIC: //https://github.com/vgmtrans/vgmtrans/blob/master/src/main/formats/AkaoSeq.cpp // header datas byte[] header = buffer.ReadBytes(4); // AKAO ushort fileId = buffer.ReadUInt16(); ushort byteLen = buffer.ReadUInt16(); ushort reverb = buffer.ReadUInt16(); // 0x0500 | Just on case 0x0400 (MUSIC000.DAT), maybe it refer to the WAVE0005.DAT sample collection buffer.ReadBytes(6); // padding uint unk1 = buffer.ReadUInt32(); // never > 127, maybe a general volume ? or something like that uint sampleSet = buffer.ReadUInt32(); // ID of the WAVE*.DAT in the SOUND folder buffer.ReadBytes(8); // padding int bnumt = buffer.ReadInt32(); uint numTrack = ToolBox.GetNumPositiveBits(bnumt); short unk3 = buffer.ReadInt16(); // (0, -1, 255 or 16383) from MUSIC050 to MUSIC101 => unk3 != 0 // when != 0 it seems like it's not a "music" but more like a sounds store for maps ambiance or monsters // when != 0, in most of case there is no instruments set nor a drum (excp 68, 69) and Unknowns AKAO events occur a lot. // in these cases you really feal that it doesn't really make sence to out a midi file and less a wav... // when != 0 => sampleSet 17 to 25 // Case 255 (66 to 73, 82, 83, 96, 97) // Case 16383 (78 to 81, 88 to 91) // Case -1 all others from 50 to 101 buffer.ReadBytes(10); // padding uint ptr1 = buffer.ReadUInt32() + 0x30; // instruments pointer uint ptr2 = buffer.ReadUInt32() + 0x34; // Drums pointer buffer.ReadBytes(8); // padding ushort jump = buffer.ReadUInt16(); long basePtr = buffer.BaseStream.Position; long musInstrPtr = buffer.BaseStream.Position + jump - 2; if (true) { Debug.Log(string.Concat("AKAO from : ", FileName, " FileSize = ", FileSize, " | reverb : ", reverb, " numTrack : ", numTrack, " sampleSet : ", sampleSet, "\r\ninstruments at : ", ptr1, " Drums at : ", ptr2, " musInstrPtr : ", musInstrPtr, " | unk1 : ", unk1, " unk3 : ", unk3)); } ushort[] tracksPtr = new ushort[numTrack]; tracksPtr[0] = (ushort)musInstrPtr; for (uint i = 0; i < numTrack - 1; i++) { tracksPtr[i + 1] = (ushort)((basePtr + i * 2) + buffer.ReadUInt16()); } // music instuctions begin here, MIDI like format, we don't care yet, so let's jump uint instrCount = 0; // Instruments if (ptr1 > 0x30) { buffer.BaseStream.Position = ptr1; // Instruments Header always 0x20 ? List <ushort> instrPtrs = new List <ushort>(); for (int i = 0; i < 0x10; i++) { ushort instrPtr = buffer.ReadUInt16(); if (instrPtr != 0xFFFF) { instrPtrs.Add(instrPtr); } else { // Padding } } instrCount = (uint)instrPtrs.Count; if (ptr2 > 0x34) { instrCount++; } if (UseDebug) { Debug.Log("Instruments number : " + instrCount); } instruments = new List <AKAOInstrument>(); for (int i = 0; i < instrPtrs.Count; i++) { AKAOInstrument instrument = new AKAOInstrument((uint)i, AKAOInstrument.InstrumentType.INSTR_MELODIC); instrument.name = "Instrument #" + (ushort)i; long instrStart = ptr1 + 0x20 + instrPtrs[i]; long instrEnd; if (i < instrPtrs.Count - 1) { instrEnd = ptr1 + 0x20 + instrPtrs[i + 1]; } else { if (ptr2 > 0x34) { instrEnd = ptr2; } else { instrEnd = byteLen; } } int instrRegLoop = (int)(instrEnd - instrStart) / 0x08; if (UseDebug) { Debug.Log(string.Concat("Instrument #", i, " Regions count : ", instrRegLoop - 1)); } instrument.regions = new AKAORegion[instrRegLoop - 1]; // -1 because the last 8 bytes are padding for (int j = 0; j < instrRegLoop - 1; j++) { AKAORegion reg = new AKAORegion(); reg.FeedMelodic(buffer.ReadBytes(8)); instrument.regions[j] = reg; if (UseDebug) { Debug.Log(reg.ToString()); } } buffer.ReadBytes(8); // 0000 0000 0000 0000 padding instruments.Add(instrument); } } // Drum if (ptr2 > 0x34) { if (buffer.BaseStream.Position != ptr2) { buffer.BaseStream.Position = ptr2; } // Special case when there is no melodic instruments if (instruments == null) { instrCount++; instruments = new List <AKAOInstrument>(); } AKAOInstrument drum = new AKAOInstrument(instrCount - 1, AKAOInstrument.InstrumentType.INSTR_DRUM); drum.name = "Drum"; int drumRegLoop = (int)(byteLen - ptr2) / 0x08; if (UseDebug) { Debug.Log(string.Concat("Drum Regions count : ", drumRegLoop - 1)); } List <AKAORegion> dr = new List <AKAORegion>(); for (int j = 0; j < drumRegLoop - 1; j++) { byte[] b = buffer.ReadBytes(8); if (b[0] == 0xFF && b[1] == 0xFF && b[2] == 0xFF && b[3] == 0xFF && b[4] == 0xFF && b[5] == 0xFF && b[6] == 0xFF && b[7] == 0xFF) { break; } if (b[0] > 0 && b[1] > 0 && b[6] > 0 && b[7] > 0) { AKAORegion dregion = new AKAORegion(); dregion.FeedDrum(b, j); dr.Add(dregion); if (UseDebug) { Debug.Log(dregion.ToString()); } } } drum.regions = dr.ToArray(); instruments.Add(drum); } long end = 0; if (ptr1 > 0x30) { end = ptr1; } else if (ptr2 > 0x34) { end = ptr2; } else { end = buffer.BaseStream.Length; } composer = new AKAOComposer(buffer, musInstrPtr, end, instrCount, numTrack, tracksPtr, FileName, true); // AKAO from : WAVE0000 startingArticulationId = 0 // AKAO from : WAVE0005 startingArticulationId = 32 // AKAO from : WAVE0032 startingArticulationId = 32 // All other startingArticulationId = 64 // AKAO from : WAVE0200 startingArticulationId = 128 // So we seek for the appropriate WAVE*.DAT in the SOUND folder string[] hash = FilePath.Split("/"[0]); hash[hash.Length - 2] = "SOUND"; AKAO[] sampleCollections = new AKAO[3]; // Program from 32 to 63 hash[hash.Length - 1] = "WAVE0005.DAT"; // wave 005 or wave 032 ? (5 seems good) AKAO SampleColl32 = new AKAO(); SampleColl32.UseDebug = UseDebug; SampleColl32.Parse(String.Join("/", hash), AKAO.SOUND); sampleCollections[0] = SampleColl32; string zz = "0"; if (sampleSet < 100) { zz += "0"; } if (sampleSet < 10) { zz += "0"; } hash[hash.Length - 1] = "WAVE" + zz + sampleSet + ".DAT"; string samplePath = String.Join("/", hash); bool test = File.Exists(samplePath); // Program from 64 to 95 or 127 AKAO SampleColl64 = new AKAO(); SampleColl64.UseDebug = UseDebug; SampleColl64.Parse(samplePath, AKAO.SOUND); sampleCollections[1] = SampleColl64; // Additionnal Collection, somztimes usefull for drum kit or A1 program change if (SampleColl64.articulations.Length < 64) { hash[hash.Length - 1] = "WAVE0091.DAT"; AKAO addiColl = new AKAO(); addiColl.UseDebug = UseDebug; addiColl.Parse(String.Join("/", hash), AKAO.SOUND); sampleCollections[2] = addiColl; } if (composer.A1Calls.Count > 0) { // we need to add new instruments with an unique region foreach (uint iid in composer.A1Calls) { AKAOInstrument A1Instrument = new AKAOInstrument(iid); A1Instrument.name = "A1 Instrument #" + (ushort)iid; A1Instrument.a1 = true; A1Instrument.regions = new AKAORegion[1]; AKAORegion defaultRegion = new AKAORegion(); defaultRegion.articulationId = (byte)iid; A1Instrument.regions[0] = defaultRegion; if (instruments == null) { instruments = new List <AKAOInstrument>(); } instruments.Add(A1Instrument); } } SF2 sf2 = null; if (bWAV || bSF2 || bDLS) { sf2 = SoundFundry(this, sampleCollections); } if (bMID || bWAV) { if (unk3 != 0) { bWAV = false; // we don't want 1Gb crap .wav } composer.Synthetize(bMID, bWAV, sf2); } break; case AKAOType.SOUND: // Samples Collection // header datas header = buffer.ReadBytes(4); // AKAO ushort sampleId = buffer.ReadUInt16(); buffer.ReadBytes(10); // padding unk1 = buffer.ReadByte(); // almost always 0 (but one case 48 in WAVE0032) unk3 = buffer.ReadByte(); // almost always 81 (two cases 49 (WAVE0000, WAVE0005), one case 16 in WAVE0032, one case 177 in WAVE0200) buffer.ReadBytes(2); // padding var sampleSize = buffer.ReadUInt32(); startingArticulationId = buffer.ReadUInt32(); var numArts = buffer.ReadUInt32(); // mostly 32, sometimes 64, one case 48 (WAVE0071), one case 96 (WAVE0200) /* List of 64 arts * WAVE0044 * WAVE0045 * WAVE0046 * WAVE0053 * WAVE0054 * WAVE0055 * WAVE0064 * WAVE0065 * WAVE0068 * WAVE0069 * WAVE0091 * WAVE0097 * WAVE0099 */ buffer.ReadBytes(32); // padding if (UseDebug) { Debug.Log(string.Concat("AKAO from : ", FileName, " len = ", FileSize, " ID : ", sampleId, " unk1 : ", unk1, " unk3 : ", unk3, " sampleSize : ", sampleSize, " stArtId : ", startingArticulationId, " numArts : ", numArts)); } // Articulations section here articulations = new AKAOArticulation[numArts]; for (uint i = 0; i < numArts; i++) { AKAOArticulation arti = new AKAOArticulation(buffer, startingArticulationId + i); articulations[i] = arti; if (UseDebug) { //Debug.Log(arti.ToString()); } } // Samples section here ulong samStart = (ulong)buffer.BaseStream.Position; // First we need to determine the start and the end of the samples, 16 null bytes indicate a new sample, so lets find them. List <long> samPtr = new List <long>(); List <long> samEPtr = new List <long>(); while (buffer.BaseStream.Position < buffer.BaseStream.Length) { if (buffer.ReadUInt64() + buffer.ReadUInt64() == 0) { if (samPtr.Count > 0) { //samEPtr.Add(buffer.BaseStream.Position - 0x20); samEPtr.Add(buffer.BaseStream.Position - 0x10); } samPtr.Add(buffer.BaseStream.Position - 0x10); //samPtr.Add(buffer.BaseStream.Position); } } samEPtr.Add(buffer.BaseStream.Length); // Let's loop again to get samples int numSam = samPtr.Count; samples = new AKAOSample[numSam]; for (int i = 0; i < numSam; i++) { buffer.BaseStream.Position = samPtr[i]; int size = (int)(samEPtr[i] - samPtr[i]); byte[] dt = buffer.ReadBytes(size); AKAOSample sam = new AKAOSample(string.Concat(FileName, " Sample #", (ushort)i), dt, (ulong)samPtr[i]); sam.index = i; samples[i] = sam; if (UseDebug && bWAV) { WAV wavSam = sam.ConvertToWAV(); wavSam.SetName(FileName + "_Sample_" + i); ToolBox.DirExNorCreate(Application.dataPath + "/../Assets/Resources/Sounds/SampleColl/"); wavSam.WriteFile(Application.dataPath + "/../Assets/Resources/Sounds/SampleColl/" + FileName + "_Sample_" + i + ".wav", wavSam.Write()); } } // now to verify and associate each articulation with a sample index value // for every sample of every instrument, we add sample_section offset, because those values // are relative to the beginning of the sample section for (uint i = 0; i < articulations.Length; i++) { for (uint l = 0; l < samples.Length; l++) { //if (articulations[i].sampleOff + samStart + 0x10 == samples[l].offset) if (articulations[i].sampleOff + samStart == samples[l].offset) { articulations[i].sampleNum = l; articulations[i].sample = samples[l]; samples[l].loopStart = articulations[i].loopPt; break; } } } break; case AKAOType.PROG: // unknown yet header = buffer.ReadBytes(4); // AKAO ushort id = buffer.ReadUInt16(); buffer.ReadUInt16(); ushort tp = buffer.ReadUInt16(); buffer.ReadUInt16(); buffer.ReadUInt32(); buffer.ReadUInt32(); buffer.ReadUInt32(); buffer.ReadUInt32(); buffer.ReadUInt32(); buffer.ReadUInt16(); byteLen = buffer.ReadUInt16(); //Debug.Log(string.Concat("AKAO PROG : id : ", id, " tp : ", tp, " byteLen : ", byteLen)); //composer = new AKAOComposer(buffer, buffer.BaseStream.Position, limit, 0, 1, new ushort[] { (ushort)buffer.BaseStream.Position }, FileName, true); //composer.Synthetize(true, false); break; case AKAOType.EFFECT: //https://github.com/vgmtrans/vgmtrans/blob/akao-ps1/src/main/formats/AkaoSeq.cpp header = buffer.ReadBytes(4); // AKAO id = buffer.ReadUInt16(); byteLen = buffer.ReadUInt16(); reverb = buffer.ReadUInt16(); bnumt = buffer.ReadInt32(); numTrack = ToolBox.GetNumPositiveBits(bnumt); buffer.ReadBytes(6); Debug.Log(string.Concat("AKAO EFFECT : id : ", id, " byteLen : ", byteLen, " reverb : ", reverb, " numTrack : ", numTrack)); composer = new AKAOComposer(buffer, buffer.BaseStream.Position, limit, 0, 1, new ushort[] { (ushort)buffer.BaseStream.Position }, FileName, true); Debug.Log(string.Concat("composer.A1Calls.Count : ", composer.A1Calls.Count)); composer.Synthetize(true, false); break; case AKAOType.SAMPLE: // similar to AKAOType.SOUND without articulations, we can output a WAV file // header datas header = buffer.ReadBytes(4); // AKAO buffer.ReadUInt16(); buffer.ReadBytes(10); // padding buffer.ReadUInt16(); // buffer.ReadBytes(2); // padding buffer.ReadUInt32(); buffer.ReadUInt32(); buffer.ReadUInt32(); buffer.ReadBytes(32); // padding buffer.ReadBytes(16); // sample padding AKAOSample sample = new AKAOSample(FileName, buffer.ReadBytes((int)(limit - buffer.BaseStream.Position)), (ulong)buffer.BaseStream.Position); WAV nw = sample.ConvertToWAV(); nw.SetName(FileName); if (UseDebug && bWAV) { ToolBox.DirExNorCreate(Application.dataPath + "/../Assets/Resources/Sounds/Effects/"); nw.WriteFile(Application.dataPath + "/../Assets/Resources/Sounds/Effects/" + FileName + ".wav", nw.Write()); } break; } }
public void Parse(BinaryReader buffer) { byte[] header = buffer.ReadBytes(4); numBones = buffer.ReadByte(); numGroups = buffer.ReadByte(); numTriangles = buffer.ReadUInt16(); numQuads = buffer.ReadUInt16(); numPolys = buffer.ReadUInt16(); numFaces = numTriangles + numQuads + numPolys; byte[][] overlays = new byte[8][]; for (int i = 0; i < 8; i++) { overlays[i] = buffer.ReadBytes(4); } buffer.ReadBytes(0x24); // Unknown buffer.ReadBytes(6); // collision size and height (shape is a cylinder) buffer.ReadBytes(2); // menu position Y buffer.ReadBytes(12); // Unknown buffer.ReadBytes(2); // Shadow radius buffer.ReadBytes(2); // Shadow size increase rate buffer.ReadBytes(2); // Shadow size decrease rate buffer.ReadBytes(4); // Unknown buffer.ReadBytes(2); // Menu scale buffer.ReadBytes(2); // Unknown buffer.ReadBytes(2); // Target sphere position Y buffer.ReadBytes(8); // Unknown if (UseDebug) { Debug.Log(FileName); Debug.Log("numBones : " + numBones); Debug.Log("numGroups : " + numGroups); Debug.Log("numTriangles : " + numTriangles); Debug.Log("numQuads : " + numQuads); Debug.Log("numPolys : " + numPolys); Debug.Log("numFaces : " + numFaces); } // LBA XX_BTX.SEQ (battle animations first one is actually XX_COM.SEQ) certainly one weapon type for each file /* * LBA SEQ : 98646 OBJ\01_COM.SEQ * LBA SEQ : 0 * LBA SEQ : 98599 OBJ\01_BT2.SEQ * LBA SEQ : 0 * LBA SEQ : 98611 OBJ\01_BT4.SEQ * LBA SEQ : 0 * LBA SEQ : 98623 OBJ\01_BT6.SEQ * LBA SEQ : 0 * LBA SEQ : 0 * LBA SEQ : 0 * LBA SEQ : 98635 OBJ\01_BTA.SEQ * LBA SEQ : 0 */ for (int i = 0; i < 12; i++) { buffer.ReadUInt32(); } for (int i = 0; i < 12; i++) { buffer.ReadBytes(2); // chain attack animation ID } for (int i = 0; i < 4; i++) { buffer.ReadBytes(4); // LBA XXSP0X.SEQ (special attack animations) } buffer.ReadBytes(0x20); // unknown (probably more LBA tables, there are also special attack ids stored here.) file like 00BT1B00.SEQ (Battle Techniques) long dec = buffer.BaseStream.Position + 4; long magicPtr = buffer.ReadUInt32() + dec; for (int i = 0; i < 0x18; i++) { buffer.ReadBytes(2); // unknown (noticeable effects when casting spell } long AKAOPtr = buffer.ReadUInt32() + dec; long groupPtr = buffer.ReadUInt32() + dec; long vertexPtr = buffer.ReadUInt32() + dec; long facePtr = buffer.ReadUInt32() + dec; if (UseDebug) { Debug.Log("magicPtr : " + magicPtr); Debug.Log("AKAOPtr : " + AKAOPtr); Debug.Log("groupPtr : " + groupPtr); Debug.Log("vertexPtr : " + vertexPtr); Debug.Log("facePtr : " + facePtr); } // Bones section bones = new List <VSBone>(); for (uint i = 0; i < numBones; i++) { VSBone bone = new VSBone(); bone.index = i; bone.name = "bone_" + i; bone.length = buffer.ReadInt16(); //Debug.LogWarning("bone "+i+ " .length : "+ bone.length); buffer.ReadUInt16(); // always 0xFFFF bone.parentIndex = buffer.ReadByte(); if (bone.parentIndex > numBones) { bone.parentIndex = -1; } //Debug.LogWarning("bone.parentIndex : " + bone.parentIndex); // https://github.com/morris/vstools/blob/master/src/WEPBone.js byte[] offset = buffer.ReadBytes(3); bone.offset = new Vector3(offset[0], offset[1], offset[2]); bone.mode = buffer.ReadByte(); // 0 - 2 normal ? // 3 - 6 normal + roll 90 degrees // 7 - 255 absolute, different angles buffer.ReadBytes(7); // always 0000000 bones.Add(bone); } // Group section if (buffer.BaseStream.Position != groupPtr) { if (UseDebug) { Debug.Log(buffer.BaseStream.Position + " != " + groupPtr); Debug.Log("le pointeur groupPtr n'est pas à la bonne place"); } buffer.BaseStream.Position = groupPtr; } groups = new List <VSGroup>(); for (uint i = 0; i < numGroups; i++) { VSGroup group = new VSGroup(); group.boneIndex = buffer.ReadInt16(); group.numVertices = buffer.ReadUInt16(); if (group.boneIndex != -1) { group.bone = bones[group.boneIndex]; } groups.Add(group); } // Vertices section if (buffer.BaseStream.Position != vertexPtr) { if (UseDebug) { Debug.Log(buffer.BaseStream.Position + " != " + vertexPtr); Debug.Log("le pointeur vertexPtr n'est pas à la bonne place"); } buffer.BaseStream.Position = vertexPtr; } vertices = new List <VSVertex>(); uint numVertices = groups[groups.Count - 1].numVertices; if (UseDebug) { Debug.Log("numVertices : " + numVertices); } int g = 0; for (uint i = 0; i < numVertices; i++) { if (i >= groups[g].numVertices) { g++; } VSVertex vertex = new VSVertex(); vertex.group = groups[g]; vertex.bone = vertex.group.bone; BoneWeight bw = new BoneWeight(); bw.boneIndex0 = (int)vertex.group.bone.index; bw.weight0 = 1; vertex.boneWeight = bw; int x = buffer.ReadInt16(); int y = buffer.ReadInt16(); int z = buffer.ReadInt16(); buffer.ReadInt16(); vertex.position = -new Vector3(x, y, z) / 100; vertices.Add(vertex); } // Polygone section if (buffer.BaseStream.Position != facePtr) { if (UseDebug) { Debug.Log(buffer.BaseStream.Position + " != " + facePtr); Debug.Log("le pointeur facePtr n'est pas à la bonne place"); } buffer.BaseStream.Position = facePtr; } faces = new List <VSFace>(); if (excpFaces) { if (UseDebug) { Debug.LogWarning("------------- TRIANGLES ----------------------"); Debug.LogWarning(buffer.BaseStream.Position); } for (uint i = 0; i < numTriangles; i++) { faces.Add(ParseColoredFace(buffer, 3)); } if (UseDebug) { Debug.LogWarning("------------- QUAD ----------------------"); Debug.LogWarning(buffer.BaseStream.Position); } for (uint i = 0; i < numQuads; i++) { faces.Add(ParseColoredFace(buffer, 4)); } if (UseDebug) { Debug.LogWarning("------------- POLY ----------------------"); Debug.LogWarning(buffer.BaseStream.Position); } for (uint i = 0; i < numPolys; i++) { long polyDec = buffer.BaseStream.Position; byte[] bytes = buffer.ReadBytes(24); if (bytes[11] == 0x34) { // its a triangle buffer.BaseStream.Position = polyDec; faces.Add(ParseColoredFace(buffer, 3)); } if (bytes[11] == 0x3C) { // its a quad buffer.BaseStream.Position = polyDec; faces.Add(ParseColoredFace(buffer, 4)); } } } else { for (uint i = 0; i < numFaces; i++) { VSFace face = new VSFace(); long polyDec = buffer.BaseStream.Position; // 4 bytes face.type = buffer.ReadByte(); face.size = buffer.ReadByte(); face.side = buffer.ReadByte(); face.alpha = buffer.ReadByte(); /* * Triangles * 24-10-04-00-C406-CC06-C806-08-78-05-6D-05-7C * 24-10-04-00-D006-D806-D406-13-6D-0A-6D-0C-78 * 24-10-04-00-D406-E006-DC06-13-78-13-6D-0F-78 * 24-10-04-00-D406-D806-E006-0F-78-13-6D-0C-78 * */ // if (UseDebug)Debug.Log(string.Concat("##### At : ", polyDec, " face# ", i, " face.type : ", face.type, " face.size : ", face.size, " face.side : ", face.side, " face.alpha : ", face.alpha)); uint[] table = new uint[256]; table[36] = 3; table[44] = 4; face.verticesCount = 0; // fallback if (table[face.type] != 0) { face.verticesCount = table[face.type]; } else { if (UseDebug) { Debug.LogError("#### Unknown face type !"); } } // 6 or 8 bytes for (uint j = 0; j < face.verticesCount; j++) { int vId = buffer.ReadUInt16() / 4; face.vertices.Add(vId); // if (UseDebug) Debug.Log("vId : " + j + " - " + vId); } // 6 or 8 bytes for (uint j = 0; j < face.verticesCount; j++) { int u = buffer.ReadByte(); int v = buffer.ReadByte(); face.uv.Add(new Vector2(u, v)); // if (UseDebug) Debug.Log("u : " + u + " v : " + v); } faces.Add(face); } } // AKAO section if (buffer.BaseStream.Position != AKAOPtr) { if (UseDebug) { Debug.LogWarning(buffer.BaseStream.Position + " != " + AKAOPtr + " le pointeur AKAOPtr n'est pas à la bonne place " + FileName); } buffer.BaseStream.Position = AKAOPtr; } uint akaoNum = buffer.ReadUInt32(); if (UseDebug) { Debug.Log(string.Concat("akaoNum : ", akaoNum)); } uint[] akaoFramesPtr = new uint[akaoNum]; // one pointer for AKAO header, a second for AKAO datas for (uint j = 0; j < akaoNum; j++) { akaoFramesPtr[j] = buffer.ReadUInt32(); } for (uint j = 0; j < akaoNum; j += 2) { long limit; if (j < akaoFramesPtr.Length - 1) { limit = AKAOPtr + akaoFramesPtr[j + 1]; // somtimes there are empty ptrs at the begining so we skip if (akaoFramesPtr[j + 1] > 0) { AKAO akao = new AKAO(); akao.FileName = string.Concat(FileName, "_Akao_", j); akao.UseDebug = true; akao.Parse(buffer, AKAO.UNKNOWN, limit); } } else { limit = magicPtr; AKAO akao = new AKAO(); akao.FileName = string.Concat(FileName, "_Akao_", j); akao.UseDebug = true; akao.Parse(buffer, AKAO.UNKNOWN, limit); } } // Magic section if (buffer.BaseStream.Position != magicPtr) { if (UseDebug) { Debug.LogWarning(buffer.BaseStream.Position + " != " + magicPtr + " le pointeur magicPtr n'est pas à la bonne place " + FileName); } buffer.BaseStream.Position = magicPtr; } long num = buffer.ReadUInt32(); long magicSize = buffer.ReadUInt32(); //size of magic effect section (doesnt include this 8 byte header) long num1 = buffer.ReadUInt32(); long num2 = buffer.ReadUInt32(); long num3 = buffer.ReadUInt32(); if (UseDebug) { Debug.Log(string.Concat("Magic num : ", num, " magicSize : ", magicSize, " num1 : " + num1, " num2 : " + num2, " num3 : " + num3)); } if (buffer.BaseStream.Position + (magicSize - 12) < buffer.BaseStream.Length) { buffer.BaseStream.Position = buffer.BaseStream.Position + (magicSize - 12); } if (UseDebug) { Debug.Log(string.Concat("buffer.BaseStream.Position : ", buffer.BaseStream.Position, " buffer.BaseStream.Length : ", buffer.BaseStream.Length)); } if (buffer.BaseStream.Position + 8 < buffer.BaseStream.Length) { // Textures section tim = new TIM(); tim.FileName = FileName; tim.ParseSHP(buffer, excpFaces); texture = tim.DrawSHP(true); } //buffer.Close(); }