https://youtu.be/VBqYEaqCzxY

find encryption keys - not very good idea. Such things are better kept private.JohnHudeski wrote: ↑Tue May 14, 2019 11:07 amIs it possible to do a tutorial on how to find encryption keys
or how to find out how exe read un-common structures
Code: Select all
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Globalization;
namespace HJA_test
{
class BinaryReaderBE : BinaryReader
{
private byte[] a16 = new byte[2];
private byte[] a32 = new byte[4];
private byte[] a64 = new byte[8];
public BinaryReaderBE(System.IO.Stream stream) : base(stream) { }
public override float ReadSingle()
{
a32 = base.ReadBytes(4);
Array.Reverse(a32);
return BitConverter.ToSingle(a32, 0);
}
public override UInt16 ReadUInt16()
{
a16 = base.ReadBytes(2);
Array.Reverse(a16);
return BitConverter.ToUInt16(a16, 0);
}
public override Int16 ReadInt16()
{
a16 = base.ReadBytes(2);
Array.Reverse(a16);
return BitConverter.ToInt16(a16, 0);
}
public override Int32 ReadInt32()
{
a32 = base.ReadBytes(4);
Array.Reverse(a32);
return BitConverter.ToInt16(a32, 0);
}
public override UInt32 ReadUInt32()
{
a32 = base.ReadBytes(4);
Array.Reverse(a32);
return BitConverter.ToUInt32(a32, 0);
}
public override long ReadInt64()
{
a64 = base.ReadBytes(8);
Array.Reverse(a64);
return BitConverter.ToInt64(a64, 0);
}
}
class Program
{
static void Main(string[] args)
{
NumberFormatInfo nfi = new NumberFormatInfo();
nfi.NumberDecimalSeparator = ".";
float x = 0;
var sw = new StreamWriter(Path.GetFileNameWithoutExtension(args[0]) + ".obj");
sw.Write(x.ToString("0.######", nfi));
}
}
}
Code: Select all
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Globalization;
namespace TM2012
{
class BinaryReaderBE : BinaryReader
{
private byte[] a16 = new byte[2];
private byte[] a32 = new byte[4];
private byte[] a64 = new byte[8];
public BinaryReaderBE(System.IO.Stream stream) : base(stream) { }
public override float ReadSingle()
{
a32 = base.ReadBytes(4);
Array.Reverse(a32);
return BitConverter.ToSingle(a32, 0);
}
public override UInt16 ReadUInt16()
{
a16 = base.ReadBytes(2);
Array.Reverse(a16);
return BitConverter.ToUInt16(a16, 0);
}
public override Int16 ReadInt16()
{
a16 = base.ReadBytes(2);
Array.Reverse(a16);
return BitConverter.ToInt16(a16, 0);
}
public override Int32 ReadInt32()
{
a32 = base.ReadBytes(4);
Array.Reverse(a32);
return BitConverter.ToInt16(a32, 0);
}
public override UInt32 ReadUInt32()
{
a32 = base.ReadBytes(4);
Array.Reverse(a32);
return BitConverter.ToUInt32(a32, 0);
}
public override long ReadInt64()
{
a64 = base.ReadBytes(8);
Array.Reverse(a64);
return BitConverter.ToInt64(a64, 0);
}
}
class tm2012
{
static void Main(string[] args)
{
NumberFormatInfo nfi = new NumberFormatInfo();
nfi.NumberDecimalSeparator = ".";
int i;
float x, y, z;
var fs = new FileStream("C:\\ui.ngp", FileMode.Open, FileAccess.Read);
var br = new BinaryReaderBE(fs);
var sw = new StreamWriter("ui.obj");
fs.Seek(0x29ef30, SeekOrigin.Begin);
int nv = 0x12f8;
for (i = 0; i < nv; i++)
{
x = br.ReadInt16() / 4096f;
y = br.ReadInt16() / 4096f;
z = br.ReadInt16() / 4096f;
sw.Write("v " + x.ToString("0.######", nfi));
sw.Write(" " + y.ToString("0.######", nfi));
sw.Write(" " + z.ToString("0.######", nfi));
sw.WriteLine();
}
fs.Seek(0x298500, SeekOrigin.Begin);
int nf = 0x3513 / 3;
int f1, f2, f3;
for (i = 0; i < nf; i++)
{
f1 = br.ReadUInt16() + 1; f2 = br.ReadUInt16() + 1; f3 = br.ReadUInt16() + 1;
sw.WriteLine("f " + f1 + " " + f2 + " " + f3);
}
sw.Close();
}
}
}
Code: Select all
var br = new BinaryReaderBE(fs);
Code: Select all
var br = new BinaryReader(fs);
Code: Select all
import bpy
import struct
from pathlib import Path
SEEK_ABS = 0
SEEK_REL = 1
SEEK_END = 2
class fopen:
little_endian = True
file = ""
data = bytes()
size = 0
pos = 0
isGood = False
def __init__(self, filename = None, mode='rb', isLittleEndian=True):
if filename != None and Path(filename).is_file():
self.data = open(filename, mode).read()
self.size = len(self.data)
self.pos = 0
self.file = filename
self.little_endian = isLittleEndian
self.isGood = True
return None
def read_and_unpack(self, unpack, size):
'''
Charactor, Byte-order
@, native, native
=, native, standard
<, little endian
>, big endian
!, network
Format, C-type, Python-type, Size[byte]
c, char, byte, 1
b, signed char, integer, 1
B, unsigned char, integer, 1
h, short, integer, 2
H, unsigned short, integer, 2
i, int, integer, 4
I, unsigned int, integer, 4
f, float, float, 4
d, double, float, 8
'''
value = 0
if self.size > 0 and self.pos + size < self.size:
value = struct.unpack_from(unpack, self.data, self.pos)[0]
self.pos += size
return value
def set_pointer(self, offset):
self.pos = offset
return None
def fseek(bitStream, offset, dir):
if dir == 0:
bitStream.set_pointer(offset)
elif dir == 1:
bitStream.set_pointer(bitStream.pos + offset)
elif dir == 2:
bitStream.set_pointer(bitStream.pos - offset)
return None
def readShort(bitStream, isSigned=0):
fmt = '>' if not bitStream.little_endian else '<'
fmt += 'h' if isSigned == 0 else 'H'
return (bitStream.read_and_unpack(fmt, 2))
def readLong(bitStream, isSigned=0):
fmt = '>' if not bitStream.little_endian else '<'
fmt += 'i' if isSigned == 0 else 'I'
return (bitStream.read_and_unpack(fmt, 4))
def readFloat(bitStream):
fmt = '>f' if not bitStream.little_endian else '<f'
return (bitStream.read_and_unpack(fmt, 4))
def mesh (vertices = [], faces = []):
bpy.context.view_layer.objects.active = None
msh = bpy.data.meshes.new('Mesh')
msh.from_pydata(vertArray, [], faceArray)
msh.update()
obj = bpy.data.objects.new('Object', msh)
bpy.data.collections[bpy.context.view_layer.active_layer_collection.name].objects.link(obj)
f = fopen('C:\\Users\\Corey\\Desktop\\grimlock\\models\\vis1\\geo_veh_c_sec_wheel.xgm', 'rb')
fseek(f, 0x1D8, SEEK_ABS)
# Reading Buffer Info
vertex_buffer_size = readLong(f)
index_buffer_size = readLong(f)
# Calculate the Counts from Buffer Info
vertex_buffer_stride = 24
index_buffer_stride = 2
vertex_count = int(vertex_buffer_size / vertex_buffer_stride)
index_count = int(index_buffer_size / index_buffer_stride / 3)
# Read The buffers
vertArray = []
faceArray = []
fseek(f, 0x29C, SEEK_ABS)
for i in range(0, vertex_count):
vertArray.append( (readFloat(f), readFloat(f), readFloat(f)) )
fseek(f, (vertex_buffer_stride - 12), SEEK_REL)
for i in range(0,index_count):
faceArray.append((readShort(f), readShort (f), readShort (f)))
# make mesh
mesh(vertArray, faceArray)
Code: Select all
f = fopen "C:\\Users\\Corey\\Desktop\\grimlock\\models\\vis1\\geo_veh_c_sec_wheel.xgm" "rb"
SEEK_ABS = #seek_set
SEEK_REL = #seek_cur
SEEK_END = #seek_end
fseek f 0x1D8 SEEK_ABS
-- Reading Buffer Info
vertex_buffer_size = readLong f
index_buffer_size = readLong f
-- Calculate the Counts from Buffer Info
vertex_buffer_stride = 24
index_buffer_stride = 2
vertex_count = vertex_buffer_size / vertex_buffer_stride
index_count = index_buffer_size / index_buffer_stride / 3
-- Read The buffers
vertArray = #()
faceArray = #()
fseek f 0x29C SEEK_ABS
for i = 1 to vertex_count do (
append vertArray [-readFloat f, readFloat f, readFloat f]
fseek f (vertex_buffer_stride - 12) SEEK_REL
)
for i = 1 to index_count do (
append faceArray ([readShort f, readShort f, readShort f] + 1)
)
-- import to the scene
mesh vertices:vertArray faces:faceArray
fclose f
Code: Select all
import c4d
import maxon
from c4d import *
from maxon import *
# Enums!
SEEK_ABS = 0
SEEK_REL = 1
SEEK_END = 2
class fopen: # reads file to buffer, and reads values from buffer
byteStream = maxon.BaseArray(maxon.Char)
size = 0
pos = 0
eof = False # End of Stream
bebo = False # Big Endian Byte Order
def __init__(self, filename = ""):
if filename != "":
fname = maxon.Url(filename)
if (fname.IoDetect() == maxon.IODETECT.FILE):
inputStream = fname.OpenInputStream()
self.size = inputStream.GetStreamLength()
if self.size > 0:
self.byteStream.Resize(self.size)
inputStream.ReadEOS(self.byteStream)
self.pos = 0
inputStream.Close()
def seek (self, offset, curdir = 0):
if curdir == SEEK_ABS:
self.pos = offset
elif curdir == SEEK_REL:
self.pos += offset
elif curdir == SEEK_END:
self.pos = self.size - offset
if self.pos > self.size or self.pos < 0:
self.pos = 0
self.eof = True
return not self.eof
def tell (self):
return self.pos
def unsigned_to_signed (self, n, nbits):
result = n
if (n > pow(2, nbits) / 2):
result = n - pow(2, nbits)
return result
def readByte (self, isSigned = True):
val = -1
if self.pos + 1 < self.size:
val = ord(self.byteStream[self.pos])
self.pos+=1
if isSigned: val = self.unsigned_to_signed(val, 8)
return val
def readShort (self, isSigned = True):
byteOrder = [0, 1]
if self.bebo: byteOrder = [1, 0]
val = -1
if self.pos + 1 < self.size:
val = ord(self.byteStream[self.pos + byteOrder[0]]) * 0x0001
val += ord(self.byteStream[self.pos + byteOrder[1]]) * 0x0100
self.pos+=2
if isSigned: val = self.unsigned_to_signed(val, 16)
return val
def readLong (self, isSigned = True):
byteOrder = [0, 1, 2, 3]
if self.bebo: byteOrder = [3, 2, 1, 0]
val = -1
if self.pos + 1 < self.size:
val = ord(self.byteStream[self.pos + byteOrder[0]]) * 0x00000001
val += ord(self.byteStream[self.pos + byteOrder[1]]) * 0x00000100
val += ord(self.byteStream[self.pos + byteOrder[2]]) * 0x00010000
val += ord(self.byteStream[self.pos + byteOrder[3]]) * 0x01000000
self.pos+=4
if isSigned: val = self.unsigned_to_signed(val, 32)
return val
def readFloat (self):
inputAsInt = self.readLong(False)
fraction = 0.0
for i in range(0, 23): fraction += (1 & (inputAsInt >> ((23 - i) - 1))) * (pow(2, -(i + 1)))
sign = -1
if (inputAsInt >> 31) & 0x00000001 == 0:
sign = 1
return (sign * (1 + fraction) * (pow(2, (((inputAsInt & 0x7F800000) >> 23) - 127))))
class mesh: # builds mesh into C4D
def __init__(self, vertices = [], faces = []):
if len(vertices) > 0 and len(faces) > 0:
mypoly = c4d.BaseObject(c4d.Opolygon) #Create an empty polygon object
mypoly.ResizeObject(len(vertices), len(faces)) #New number of points, New number of polygons
for i in range(0, len(vertices)):
mypoly.SetPoint(i, vertices[i])
for i in range(0, len(faces)):
mypoly.SetPolygon(i, faces[i])
doc.InsertObject(mypoly,None,None)
mypoly.Message(c4d.MSG_UPDATE)
c4d.EventAdd()
f = fopen("C:\\Users\\Corey\\Desktop\\grimlock\\models\\vis1\\geo_veh_c_sec_wheel.xgm")
f.seek(0x1D8, SEEK_ABS)
# Reading Buffer Info
vertex_buffer_size = f.readLong()
index_buffer_size = f.readLong()
# Calculate the Counts from Buffer Info
vertex_buffer_stride = 24
index_buffer_stride = 2
vertex_count = int(vertex_buffer_size / vertex_buffer_stride)
index_count = int(index_buffer_size / index_buffer_stride / 3)
# Read The buffers
vertArray = []
faceArray = []
f.seek(0x29C, SEEK_ABS)
for i in range(0, vertex_count):
vertArray.append(c4d.Vector(f.readFloat(), f.readFloat(), f.readFloat()))
f.seek(vertex_buffer_stride - 12, SEEK_REL)
for i in range(0,index_count):
faceArray.append(c4d.CPolygon(f.readShort(), f.readShort(), f.readShort()))
# make mesh
mesh(vertArray, faceArray)
Code: Select all
from inc_noesis import *
def registerNoesisTypes():
handle = noesis.register("load an xgm", ".xgm")
noesis.setHandlerTypeCheck(handle, noepyCheckType)
noesis.setHandlerLoadModel(handle, noepyLoadModel)
noesis.logPopup()
return 1
def noepyCheckType(data):
if len(data) < 12: return 0
f = NoeBitStream(data)
f.seek(8)
if f.readInt() != 0x4D534758: return 0
return 1
def noepyLoadModel(data, mdlList):
if len(data) < 12: return 0
f = NoeBitStream(data)
f.seek(8)
if f.readInt() != 0x4D534758: return 0
f.seek(0x1D8, NOESEEK_ABS)
# Reading Buffer Info
vertex_buffer_size = f.readInt()
index_buffer_size = f.readInt()
# Calculate the Counts from Buffer Info
vertex_buffer_stride = 24
index_buffer_stride = 2
vertex_count = int(vertex_buffer_size / vertex_buffer_stride)
index_count = int(index_buffer_size / index_buffer_stride / 3)
# Read The buffers
vertArray = []
faceArray = []
f.seek(0x29C, NOESEEK_ABS)
for i in range(0, vertex_count):
vertArray.append(NoeVec3((f.readFloat(), f.readFloat(), f.readFloat())))
f.seek((vertex_buffer_stride - 12), NOESEEK_REL)
for i in range(0, index_count):
f1 = f.readShort()
f2 = f.readShort()
f3 = f.readShort()
faceArray.append(f1)
faceArray.append(f3)
faceArray.append(f2)
# import to the scene
mdl = NoeModel()
mdl.setMeshes([NoeMesh(faceArray, vertArray)])
mdlList.append(mdl)
return 1
Code: Select all
# script for QuickBMS http://quickbms.aluigi.org
# Set File Number
f = 0
goto 0x1D8 f SEEK_SET
# Reading Buffer Info
get vertex_buffer_size long f
get index_buffer_size long f
# Calculate the Counts from Buffer Info
set vertex_buffer_stride long 24
set index_buffer_stride long 2
set vertex_count long vertex_buffer_size
set index_count long index_buffer_size
math vertex_count /= vertex_buffer_stride
math index_count /= index_buffer_stride
math index_count /= 3
# Set ObjFile
set obj string "o mesh"
set temp string ""
string temp p= "%c%c%c%c" 0x0D 0x0A 0x0D 0x0A
string obj += temp
# Read The buffers
set skip long 0
math skip = vertex_buffer_stride
math skip -= 12
goto 0x29C f SEEK_SET
for i = 0 < vertex_count
get pos_x long f
get pos_y long f
get pos_z long f
string temp p= "v %f" pos_x
string obj += temp
string temp p= " %f" pos_y
string obj += temp
string temp p= " %f" pos_z
string obj += temp
string temp p= "%c%c" 0x0D 0x0A
string obj += temp
goto skip f SEEK_CUR
next i
string temp p= "%c%c%c%c" 0x0D 0x0A 0x0D 0x0A
string obj += temp
for i = 0 < index_count
get face_a short f
get face_b short f
get face_c short f
math face_a += 1
math face_b += 1
math face_c += 1
string temp p= "f %i" face_a
string obj += temp
string temp p= " %i" face_c
string obj += temp
string temp p= " %i" face_b
string obj += temp
string temp p= "%c%c" 0x0D 0x0A
string obj += temp
next i
// write the OBJ
strlen obj_size obj
putvarchr MEMORY_FILE obj_size 0
put obj string MEMORY_FILE
get NAME basename
string file_name p= "%s.obj" NAME
log file_name 0 obj_size MEMORY_FILE
Code: Select all
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
#define SEEK_ABS 0
#define SEEK_REL 1
#define SEEK_END 2
struct bytes {
ifstream file;
bool isGood;
bytes (string filename) {
file.open (filename.c_str(), std::ifstream::binary);
isGood = file.good();
}
~bytes () {
file.close();
}
int16_t readInt16 () {
int32_t input;
file.read(reinterpret_cast<char*>(&input), sizeof(int16_t));
return input;
}
int32_t readInt32 () {
int32_t input;
file.read(reinterpret_cast<char*>(&input), sizeof(int32_t));
return input;
}
float readFloat32 () {
float input;
file.read(reinterpret_cast<char*>(&input), sizeof(input));
return input;
}
void seek (int offset, int dir = 0) {
if (dir == SEEK_ABS) {
file.seekg(offset, file.beg);
}
else if (dir == SEEK_REL) {
file.seekg(offset, file.cur);
}
else if (dir == SEEK_END) {
file.seekg(offset, file.end);
}
}
};
int main() {
// Open File
bytes f("C:\\Users\\Corey\\Desktop\\grimlock\\models\\vis1\\geo_veh_c_sec_wheel.xgm");
// Check is open
if (!f.isGood) {return 0;}
// Read the Buffer Info
f.seek(0x1D8);
int vertex_buffer_size = f.readInt32();
int index_buffer_size = f.readInt32();
// Calculate the Counts from Buffer Info
int vertex_buffer_stride = 24;
int index_buffer_stride = 2;
int vertex_count = vertex_buffer_size / vertex_buffer_stride;
int index_count = index_buffer_size / index_buffer_stride / 3;
cout << "vertex_buffer_size: " << vertex_buffer_size << endl;
cout << "index_buffer_size: " << index_buffer_size << endl;
// Read Buffer
f.seek(0x29C);
// write the OBJ
std::ofstream out;
out.open ("C:\\Users\\Corey\\Desktop\\grimlock\\models\\vis1\\geo_veh_c_sec_wheel.obj", std::ofstream::out);
out << "o meshobject" << endl << endl;
for (int i = 0; i < vertex_count; i++) {
out << std::setprecision(6) << "v " << f.readFloat32();
out << std::setprecision(6) << " " << f.readFloat32();
out << std::setprecision(6) << " " << f.readFloat32() << endl;
f.seek(vertex_buffer_stride - 12, SEEK_REL);
}
out << endl;
for (int i = 0; i < index_count; i++) {
out << "f " << f.readInt16() + 1;
out << " " << f.readInt16() + 1;
out << " " << f.readInt16() + 1 << endl;
}
out.close();
return 0;
}
Code: Select all
using System;
using System.IO;
using System.Text;
namespace ModelDumper {
class Program {
static int readShort(ref Byte[] buffer, ref int ptr) {
// Converts Unsigned 16bit Integer from the buffer
int word = (buffer[ptr++] & 0xFF) | ((buffer[ptr++] & 0xFF) << 8);
return word;
}
static int readLong(ref Byte[] buffer, ref int ptr) {
// Converts Unsigned 32bit Integer from the buffer
int dword = (buffer[ptr++] & 0xFF) | ((buffer[ptr++] & 0xFF) << 8) | ((buffer[ptr++] & 0xFF) << 16) | ((buffer[ptr++] & 0xFF) << 24);
return dword;
}
static double readFloat(ref Byte[] buffer, ref int ptr) {
int inputAsInt = readLong(ref buffer, ref ptr);
double fraction = 0.0F;
for (int i = 0; i < 23; i++) {
fraction += (1 & (inputAsInt >> ((23 - i) - 1))) * (Math.Pow(2, -(i + 1)));
}
int sign = -1;
if (((inputAsInt >> 31) & 0x00000001) == 0) {
sign = 1;
}
return (sign * (1 + fraction) * (Math.Pow(2, (((inputAsInt & 0x7F800000) >> 23) - 127))));
}
static void Main(string[] args) {
// Open File into a FileStream
string file = "C:\\Users\\Corey\\Videos\\Captures\\Vghd\\geo_veh_c_sec_wheel.xgm";
var fs = new FileStream(file, FileMode.Open);
var fsize = (int)fs.Length;
var buffer = new byte[fsize];
fs.Read(buffer, 0, fsize);
fs.Close();
int ptr = 0x1D8;
int vertex_buffer_size = readLong(ref buffer, ref ptr);
int index_buffer_size = readLong(ref buffer, ref ptr);
// Calculate the Counts from Buffer Info
int vertex_buffer_stride = 24;
int index_buffer_stride = 2;
int vertex_count = vertex_buffer_size / vertex_buffer_stride;
int index_count = index_buffer_size / index_buffer_stride / 3;
// Set Obj File
string objfile = "o mesh\ng mesh\n\n";
// Read The buffers
int f1;
int f2;
int f3;
ptr = 0x29C;
for (int i = 0; i < vertex_count; i++) {
objfile += "v " + (readFloat(ref buffer, ref ptr)).ToString("0.000000");
objfile += " " + (readFloat(ref buffer, ref ptr)).ToString("0.000000");
objfile += " " + (readFloat(ref buffer, ref ptr)).ToString("0.000000") + "\n";
ptr += vertex_buffer_stride - 12;
}
objfile += "\n";
for (int i = 0; i < index_count; i++) {
f1 = readShort(ref buffer, ref ptr);
f2 = readShort(ref buffer, ref ptr);
f3 = readShort(ref buffer, ref ptr);
objfile += "f " + (f1 + 1).ToString("0");
objfile += " " + (f3 + 1).ToString("0");
objfile += " " + (f2 + 1).ToString("0") + "\n";
}
// write the OBJ
using (StreamWriter outputFile = new StreamWriter(Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file) + ".obj"), false)) {
outputFile.WriteLine(objfile);
}
}
}
}
Code: Select all
Sub binToMesh()
Dim file As String: file = "C:\\Users\\Corey\\Videos\\Captures\\Vghd\\geo_veh_c_sec_wheel.xgm"
Dim F As Long: F = FreeFile
Open file For Binary As F
Seek F, 1 + &H1D8
' Reading Buffer Info
Dim vertex_buffer_size As Long: Get F, , vertex_buffer_size
Dim index_buffer_size As Long: Get F, , index_buffer_size
' Calculate the Counts from Buffer Info
Dim vertex_buffer_stride As Long: vertex_buffer_stride = 24
Dim index_buffer_stride As Long: index_buffer_stride = 2
Dim vertex_count As Long: vertex_count = CLng(CSng(vertex_buffer_size) / CSng(vertex_buffer_stride))
Dim index_count As Long: index_count = CLng(CDbl(index_buffer_size) / CDbl(index_buffer_stride) / 3#)
' Read The buffers
Seek F, 1 + &H29C
Dim objFile As String: objFile = "o mesh" + vbNewLine + "g mesh" + vbNewLine + vbNewLine
Dim pos_x As Single, pos_y As Single, pos_z As Single
Dim face_a As Integer, face_b As Integer, face_c As Integer
Dim i As Long
For i = 1 To vertex_count
Get F, , pos_x
Get F, , pos_y
Get F, , pos_z
objFile = objFile + "v " + Format$(pos_x, "0.000000")
objFile = objFile + " " + Format$(pos_y, "0.000000")
objFile = objFile + " " + Format$(pos_z, "0.000000") + vbNewLine
Seek F, Seek(F) + (vertex_buffer_stride - 12)
Next i
objFile = objFile + vbNewLine
For i = 1 To index_count
Get F, , face_a
Get F, , face_b
Get F, , face_c
objFile = objFile + "f " + CStr(face_a + 1)
objFile = objFile + " " + CStr(face_c + 1)
objFile = objFile + " " + CStr(face_b + 1) + vbNewLine
Next i
Close F
' Write Obj
Open (file + ".obj") For Output As #1
Print #1, objFile
Close #1
End Sub