Ejemplo n.º 1
0
const char *appSymbolName(address_t addr)
{
	static char	buf[256];

#if USE_DBGHELP
	if (appSymbolName(addr, ARRAY_ARG(buf)))
		return buf;
#endif

#if GET_EXTENDED_INFO
	HMODULE hModule = NULL;
	char moduleName[256];
	char *s;

	MEMORY_BASIC_INFORMATION mbi;
	if (!VirtualQuery((void*)addr, &mbi, sizeof(mbi)))
		goto simple;
	if (!(hModule = (HMODULE)mbi.AllocationBase))
		goto simple;
	if (!GetModuleFileName(hModule, ARRAY_ARG(moduleName)))
		goto simple;

//	if (s = strrchr(moduleName, '.'))	// cut extension
//		*s = 0;
	if (s = strrchr(moduleName, '\\'))
		strcpy(moduleName, s+1);		// remove "path\" part
	appSprintf(ARRAY_ARG(buf), "%s+0x%X", moduleName, (int)(addr - (size_t)hModule));
	return buf;
#endif // GET_EXTENDED_INFO

simple:
	appSprintf(ARRAY_ARG(buf), "%08X", addr);
	return buf;
}
Ejemplo n.º 2
0
/*
===============
SV_StatusString

Builds the string that is sent as heartbeats and status replies
===============
*/
static const char *SV_StatusString()
{
	guard(SV_StatusString);
	static char	status[MAX_MSGLEN - 16];

	if (sv.attractloop) return "";

	int statusLength = appSprintf(ARRAY_ARG(status), "%s\n", Cvar_BitInfo(CVAR_SERVERINFO));
	for (int i = 0; i < sv_maxclients->integer; i++)
	{
		client_t *cl = &svs.clients[i];
		if (cl->state == cs_connected || cl->state == cs_spawned)
		{
			char player[256];
			int playerLength = appSprintf(ARRAY_ARG(player), "%d %d \"%s\"\n",
				cl->edict->client->ps.stats[STAT_FRAGS], cl->ping, *cl->Name);
			if (statusLength + playerLength >= sizeof(status))
				break;		// can't hold any more
			memcpy(status + statusLength, player, playerLength+1);
			statusLength += playerLength;
		}
	}

	return status;
	unguard;
}
Ejemplo n.º 3
0
static bool ScanGameDirectory(const char *dir, bool recurse)
{
	guard(ScanGameDirectory);

	char Path[MAX_PACKAGE_PATH];
	bool res = true;
//	printf("Scan %s\n", dir);
#if _WIN32
	appSprintf(ARRAY_ARG(Path), "%s/*.*", dir);
	_finddatai64_t found;
	long hFind = _findfirsti64(Path, &found);
	if (hFind == -1) return true;
	do
	{
		if (found.name[0] == '.') continue;			// "." or ".."
		appSprintf(ARRAY_ARG(Path), "%s/%s", dir, found.name);
		// directory -> recurse
		if (found.attrib & _A_SUBDIR)
		{
			if (recurse)
				res = ScanGameDirectory(Path, recurse);
			else
				res = true;
		}
		else
			res = RegisterGameFile(Path);
	} while (res && _findnexti64(hFind, &found) != -1);
	_findclose(hFind);
#else
	DIR *find = opendir(dir);
	if (!find) return true;
	struct dirent *ent;
	while (/*res &&*/ (ent = readdir(find)))
	{
		if (ent->d_name[0] == '.') continue;			// "." or ".."
		appSprintf(ARRAY_ARG(Path), "%s/%s", dir, ent->d_name);
		// directory -> recurse
		// note: using 'stat64' here because 'stat' ignores large files
		struct stat64 buf;
		if (stat64(Path, &buf) < 0) continue;			// or break?
		if (S_ISDIR(buf.st_mode))
		{
			if (recurse)
				res = ScanGameDirectory(Path, recurse);
			else
				res = true;
		}
		else
			res = RegisterGameFile(Path);
	}
	closedir(find);
#endif
	return res;

	unguard;
}
Ejemplo n.º 4
0
void appUnwindPrefix(const char *fmt)
{
	char buf[512];
	appSprintf(ARRAY_ARG(buf), WasError ? " <- %s:" : "%s:", fmt);
	LogHistory(buf);
	WasError = false;
}
Ejemplo n.º 5
0
/** Preprocesses a shader without performing compilation, and dump it out for debugging*/
void D3D9PreProcessShader(
	const TCHAR* strFilename,
	const FString& strShaderFile,
	vector<D3DXMACRO>& Defines,
	const FD3DIncludeEnvironment& Environment,
	const TCHAR* strShaderPath
	)
{
	TRefCountPtr<ID3DXBuffer>	ShaderCode;
	TRefCountPtr<ID3DXBuffer>	ErrorText;

	FTCHARToANSI AnsiShaderFile(strShaderFile.c_str());
	FD3DIncludeEnvironment IncludeEnvironment(Environment);
	HRESULT ret = D3DXPreprocessShader( (ANSICHAR*)AnsiShaderFile, 
										strShaderFile.size(), 
										&Defines.at(0), 
										&IncludeEnvironment,
										ShaderCode.GetInitReference(),
										ErrorText.GetInitReference()
										);

	if( FAILED(ret) )
	{
		debugf(NAME_Warning, TEXT("Preprocess failed for shader %s: %s"), strFilename, ANSI_TO_TCHAR(ErrorText->GetBufferPointer()));
	}
	else
	{
		TCHAR Tmp[MAX_SPRINTF];
		appSprintf(Tmp, TEXT("%s%s.pre"), strShaderPath, strFilename);
		appSaveStringToFile(ANSI_TO_TCHAR(ShaderCode->GetBufferPointer()), Tmp);
	}
}
Ejemplo n.º 6
0
bool UIProgressDialog::Tick()
{
	char buffer[64];
	appSprintf(ARRAY_ARG(buffer), "%d MBytes", (int)(GTotalAllocationSize >> 20));
	MemoryLabel->SetText(buffer);
	appSprintf(ARRAY_ARG(buffer), "%d", UObject::GObjObjects.Num());
	ObjectsLabel->SetText(buffer);
	return PumpMessages();
}
Ejemplo n.º 7
0
bool appSymbolName(address_t addr, char *buffer, int size)
{
	InitSymbols();

	char SymBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME];
	PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)SymBuffer;
	pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
	pSymbol->MaxNameLen   = MAX_SYM_NAME;

	DWORD64 dwDisplacement = 0;
	if (SymFromAddr(hProcess, addr, &dwDisplacement, pSymbol))
	{
		char OffsetBuffer[32];
		if (dwDisplacement)
			appSprintf(ARRAY_ARG(OffsetBuffer), "+%X", dwDisplacement);
		else
			OffsetBuffer[0] = 0;

#if EXTRA_UNDECORATE
		char undecBuffer[256];
		if (UnDecorateSymbolName(pSymbol->Name, ARRAY_ARG(undecBuffer),
			UNDNAME_NO_LEADING_UNDERSCORES|UNDNAME_NO_LEADING_UNDERSCORES|UNDNAME_NO_ALLOCATION_LANGUAGE|UNDNAME_NO_ACCESS_SPECIFIERS))
		{
			StripPrefix(undecBuffer, "virtual ");
			StripPrefix(undecBuffer, "class ");
			StripPrefix(undecBuffer, "struct ");
			appSprintf(buffer, size, "%s%s", undecBuffer, OffsetBuffer);
		}
		else
		{
			appSprintf(buffer, size, "%s%s", pSymbol->Name, OffsetBuffer);
		}
#else
		appSprintf(buffer, size, "%s%s", pSymbol->Name, OffsetBuffer);
#endif // EXTRA_UNDECORATE
	}
	else
	{
		appSprintf(buffer, size, "%08X", addr);
	}
	return true;
}
Ejemplo n.º 8
0
const char* GetGameTag(int gameEnum)
{
	static char buf[64];

	int Count = ARRAY_COUNT(GListOfGames) - 1;	// exclude TABLE_END marker
	const char* value = NULL;
	for (int i = 0; i < Count; i++)
	{
		if (GListOfGames[i].Enum == gameEnum)
		{
			value = GListOfGames[i].Switch;
			break;
		}
	}
#if UNREAL4
	if (!value && gameEnum >= GAME_UE4_BASE)
	{
		// generate tag
		int ue4ver = GAME_UE4_GET_MINOR(gameEnum);
		if (gameEnum == GAME_UE4(ue4ver))
		{
			// exactly matching, i.e. not a custom UE4 version
			appSprintf(ARRAY_ARG(buf), "ue4.%d", ue4ver);
			return buf;
		}
	}
#endif // UNREAL4

	if (!value)
	{
		appSprintf(ARRAY_ARG(buf), "%X", gameEnum);
		return buf;
	}

	return value;
}
Ejemplo n.º 9
0
FArchive *appCreateFileReader(const CGameFileInfo *info)
{
	if (!info->FileSystem)
	{
		// regular file
		char buf[MAX_PACKAGE_PATH];
		appSprintf(ARRAY_ARG(buf), "%s/%s", RootDirectory, info->RelativeName);
		return new FFileReader(buf);
	}
	else
	{
		// file from virtual file system
		return info->FileSystem->CreateReader(info->RelativeName);
	}
}
Ejemplo n.º 10
0
UBOOL VGameViewportClient::Init(FString& OutError)
{
	InitStaticData();

	TCHAR Error[MAX_SPRINTF] = TEXT("");

	// add console interaction
	_Console = ExactCast<VConsole>(VObject::StaticConstructObject(VObject::LoadClass(FName(_ConsoleClassName))));
	if( _Console == NULL )
	{
		appSprintf(Error, TEXT("Failed to create console system, in that illegal console class name(%s)"), _ConsoleClassName);
		OutError = Error;
		return FALSE;
	}
	if( InsertInteraction(_Console.GetReference()) == -1 )
	{
		appSprintf(Error, TEXT("Faile to add interaction to global interaction array"));
		OutError = Error;
		return FALSE;
	}

	// add ui interaction
	_UIInteraction = ExactCast<VUIInteraction>(VObject::StaticConstructObject(VObject::LoadClass(FName(_UIInteractionClassName))));
	if( _UIInteraction == NULL )
	{
		appSprintf(Error, TEXT("Failed to create ui interaction system, in that illegal ui interaction class name(%s)"), _ConsoleClassName);
		OutError = Error;
		return FALSE;
	}
	if( InsertInteraction(_UIInteraction.GetReference()) == -1 )
	{
		appSprintf(Error, TEXT("Faile to add interaction to global interaction array"));
		OutError = Error;
		return FALSE;
	}

	// add player interaction
	_PlayerInteraction = ExactCast<VPlayerInteraction>(VObject::StaticConstructObject(VObject::LoadClass(FName(_PlayerInteractionClassName))));
	if( _PlayerInteraction == NULL )
	{
		appSprintf(Error, TEXT("Failed to create player interaction system, in that illegal player interaction class name(%s)"), _PlayerInteractionClassName);
		OutError = Error;
		return FALSE;
	}
	if( InsertInteraction(_PlayerInteraction.GetReference()) == -1 )
	{
		appSprintf(Error, TEXT("Faile to add interaction to global interaction array"));
		OutError = Error;
		return FALSE;
	}

	return TRUE;
}
Ejemplo n.º 11
0
bool UIProgressDialog::Progress(const char* package, int index, int total)
{
	// do not update UI too often
	int tick = appMilliseconds();
	if (tick - lastTick < 50)
		return true;
	lastTick = tick;

	char buffer[512];
	appSprintf(ARRAY_ARG(buffer), "%s %d/%d", DescriptionText, index+1, total);
	DescriptionLabel->SetText(buffer);

	PackageLabel->SetText(package);

	ProgressBar->SetValue((float)(index+1) / total);

	return Tick();
}
Ejemplo n.º 12
0
static void ExportMaterial(UUnrealMaterial* Mat, FArchive& Ar, int index, bool bLast)
{
	char dummyName[64];
	appSprintf(ARRAY_ARG(dummyName), "dummy_material_%d", index);

	CVec3 Color = GetMaterialDebugColor(index);
	Ar.Printf(
		"    {\n"
		"      \"name\" : \"%s\",\n"
		"      \"pbrMetallicRoughness\" : {\n"
		"        \"baseColorFactor\" : [ %g, %g, %g, 1.0 ],\n"
		"        \"metallicFactor\" : 0.1,\n"
		"        \"roughnessFactor\" : 0.5\n"
		"      }\n"
		"    }%s\n",
		Mat ? Mat->Name : dummyName,
		Color[0], Color[1], Color[2],
		bLast ? "" : ","
	);
}
Ejemplo n.º 13
0
static void ExportAnimations(ExportContext& Context, FArchive& Ar)
{
	guard(ExportAnimations);

	const CAnimSet* Anim = Context.SkelMesh->Anim;
	int NumBones = Context.SkelMesh->RefSkeleton.Num();

	// Build mesh to anim bone map

	TArray<int> BoneMap;
	BoneMap.Init(-1, NumBones);
	TArray<int> AnimBones;
	AnimBones.Empty(NumBones);

	for (int i = 0; i < NumBones; i++)
	{
		const CSkelMeshBone &B = Context.SkelMesh->RefSkeleton[i];
		for (int j = 0; j < Anim->TrackBoneNames.Num(); j++)
		{
			if (!stricmp(B.Name, Anim->TrackBoneNames[j]))
			{
				BoneMap[i] = j;			// lookup CAnimSet bone by mesh bone index
				AnimBones.Add(i);		// indicate that the bone has animation
				break;
			}
		}
	}

	Ar.Printf(
		"  \"animations\" : [\n"
	);

	int FirstDataIndex = Context.Data.Num();

	// Iterate over all animations
	for (int SeqIndex = 0; SeqIndex < Anim->Sequences.Num(); SeqIndex++)
	{
		const CAnimSequence &Seq = *Anim->Sequences[SeqIndex];

		Ar.Printf(
			"    {\n"
			"      \"name\" : \"%s\",\n",
			*Seq.Name
		);

		struct AnimSampler
		{
			enum ChannelType
			{
				TRANSLATION,
				ROTATION
			};

			int BoneNodeIndex;
			ChannelType Type;
			const CAnimTrack* Track;
		};

		TArray<AnimSampler> Samplers;
		Samplers.Empty(AnimBones.Num() * 2);

		//!! Optimization:
		//!! 1. there will be missing tracks (AnimRotationOnly etc) - drop such samplers
		//!! 2. store all time tracks in a single BufferView, all rotation tracks in another, and all position track in 3rd one - this
		//!!    will reduce amount of BufferViews in json text (combine them by data type)

		// Prepare channels array
		Ar.Printf("      \"channels\" : [\n");
		for (int BoneIndex = 0; BoneIndex < AnimBones.Num(); BoneIndex++)
		{
			int MeshBoneIndex = AnimBones[BoneIndex];
			int AnimBoneIndex = BoneMap[MeshBoneIndex];

			const CAnimTrack* Track = Seq.Tracks[AnimBoneIndex];

			int TranslationSamplerIndex = Samplers.Num();
			AnimSampler* Sampler = new (Samplers) AnimSampler;
			Sampler->Type = AnimSampler::TRANSLATION;
			Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE;
			Sampler->Track = Track;

			int RotationSamplerIndex = Samplers.Num();
			Sampler = new (Samplers) AnimSampler;
			Sampler->Type = AnimSampler::ROTATION;
			Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE;
			Sampler->Track = Track;

			// Print glTF information. Not using usual formatting here to make output a little bit more compact.
			Ar.Printf(
				"        { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } },\n",
				TranslationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "translation"
			);
			Ar.Printf(
				"        { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } }%s\n",
				RotationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "rotation", BoneIndex == AnimBones.Num()-1 ? "" : ","
			);
		}
		Ar.Printf("      ],\n");

		// Prepare samplers
		Ar.Printf("      \"samplers\" : [\n");
		for (int SamplerIndex = 0; SamplerIndex < Samplers.Num(); SamplerIndex++)
		{
			const AnimSampler& Sampler = Samplers[SamplerIndex];

			// Prepare time array
			const TArray<float>* TimeArray = (Sampler.Type == AnimSampler::TRANSLATION) ? &Sampler.Track->KeyPosTime : &Sampler.Track->KeyQuatTime;
			if (TimeArray->Num() == 0)
			{
				// For this situation, use track's time array
				TimeArray = &Sampler.Track->KeyTime;
			}
			int NumKeys = Sampler.Type == (AnimSampler::TRANSLATION) ? Sampler.Track->KeyPos.Num() : Sampler.Track->KeyQuat.Num();

			int TimeBufIndex = Context.Data.AddZeroed();
			BufferData& TimeBuf = Context.Data[TimeBufIndex];
			TimeBuf.Setup(NumKeys, "SCALAR", BufferData::FLOAT, sizeof(float));

			float RateScale = 1.0f / Seq.Rate;
			float LastFrameTime = 0;
			if (TimeArray->Num() == 0 || NumKeys == 1)
			{
				// Fill with equally spaced values
				for (int i = 0; i < NumKeys; i++)
				{
					TimeBuf.Put(i * RateScale);
				}
				LastFrameTime = NumKeys-1;
			}
			else
			{
				for (int i = 0; i < TimeArray->Num(); i++)
				{
					TimeBuf.Put((*TimeArray)[i] * RateScale);
				}
				LastFrameTime = (*TimeArray)[TimeArray->Num()-1];
			}
			// Prepare min/max values for time track, it's required by glTF standard
			TimeBuf.BoundsMin = "[ 0 ]";
			char buf[64];
			appSprintf(ARRAY_ARG(buf), "[ %g ]", LastFrameTime * RateScale);
			TimeBuf.BoundsMax = buf;

			// Try to reuse TimeBuf from previous tracks
			TimeBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex);

			// Prepare data
			int DataBufIndex = Context.Data.AddZeroed();
			BufferData& DataBuf = Context.Data[DataBufIndex];
			if (Sampler.Type == AnimSampler::TRANSLATION)
			{
				// Translation track
				DataBuf.Setup(NumKeys, "VEC3", BufferData::FLOAT, sizeof(CVec3));
				for (int i = 0; i < NumKeys; i++)
				{
					CVec3 Pos = Sampler.Track->KeyPos[i];
					TransformPosition(Pos);
					DataBuf.Put(Pos);
				}
			}
			else
			{
				// Rotation track
				DataBuf.Setup(NumKeys, "VEC4", BufferData::FLOAT, sizeof(CQuat));
				for (int i = 0; i < NumKeys; i++)
				{
					CQuat Rot = Sampler.Track->KeyQuat[i];
					TransformRotation(Rot);
					if (Sampler.BoneNodeIndex - FIRST_BONE_NODE == 0)
					{
						Rot.Conjugate();
					}
					DataBuf.Put(Rot);
				}
			}

			// Try to reuse data block as well
			DataBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex);

			// Write glTF info
			Ar.Printf(
				"        { \"input\" : %d, \"output\" : %d }%s\n",
				TimeBufIndex, DataBufIndex, SamplerIndex == Samplers.Num()-1 ? "" : ","
			);
		}
		Ar.Printf("      ]\n");

		Ar.Printf("    }%s\n", SeqIndex == Anim->Sequences.Num()-1 ? "" : ",");
	}

	Ar.Printf("  ],\n");

	unguard;
}
Ejemplo n.º 14
0
static void ExportSection(ExportContext& Context, const CBaseMeshLod& Lod, const CMeshVertex* Verts, int SectonIndex, FArchive& Ar)
{
	guard(ExportSection);

	int VertexSize = Context.IsSkeletal() ? sizeof(CSkelMeshVertex) : sizeof(CStaticMeshVertex);

	const CMeshSection& S = Lod.Sections[SectonIndex];
	bool bLast = (SectonIndex == Lod.Sections.Num()-1);

	// Remap section indices to local indices
	CIndexBuffer::IndexAccessor_t GetIndex = Lod.Indices.GetAccessor();
	TArray<int> indexRemap; // old vertex index -> new vertex index
	indexRemap.Init(-1, Lod.NumVerts);
	int numLocalVerts = 0;
	int numLocalIndices = S.NumFaces * 3;
	for (int idx = 0; idx < numLocalIndices; idx++)
	{
		int vertIndex = GetIndex(S.FirstIndex + idx);
		if (indexRemap[vertIndex] == -1)
		{
			indexRemap[vertIndex] = numLocalVerts++;
		}
	}

	// Prepare buffers
	int IndexBufIndex = Context.Data.AddZeroed();
	int PositionBufIndex = Context.Data.AddZeroed();
	int NormalBufIndex = Context.Data.AddZeroed();
	int TangentBufIndex = Context.Data.AddZeroed();

	int BonesBufIndex = -1;
	int WeightsBufIndex = -1;
	if (Context.IsSkeletal())
	{
		BonesBufIndex = Context.Data.AddZeroed();
		WeightsBufIndex = Context.Data.AddZeroed();
	}

	int UVBufIndex[MAX_MESH_UV_SETS];
	for (int i = 0; i < Lod.NumTexCoords; i++)
	{
		UVBufIndex[i] = Context.Data.AddZeroed();
	}

	BufferData& IndexBuf = Context.Data[IndexBufIndex];
	BufferData& PositionBuf = Context.Data[PositionBufIndex];
	BufferData& NormalBuf = Context.Data[NormalBufIndex];
	BufferData& TangentBuf = Context.Data[TangentBufIndex];
	BufferData* UVBuf[MAX_MESH_UV_SETS];
	BufferData* BonesBuf = NULL;
	BufferData* WeightsBuf = NULL;

	PositionBuf.Setup(numLocalVerts, "VEC3", BufferData::FLOAT, sizeof(CVec3));
	NormalBuf.Setup(numLocalVerts, "VEC3", BufferData::FLOAT, sizeof(CVec3));
	TangentBuf.Setup(numLocalVerts, "VEC4", BufferData::FLOAT, sizeof(CVec4));
	for (int i = 0; i < Lod.NumTexCoords; i++)
	{
		UVBuf[i] = &Context.Data[UVBufIndex[i]];
		UVBuf[i]->Setup(numLocalVerts, "VEC2", BufferData::FLOAT, sizeof(CMeshUVFloat));
	}

	if (Context.IsSkeletal())
	{
		BonesBuf = &Context.Data[BonesBufIndex];
		WeightsBuf = &Context.Data[WeightsBufIndex];
		BonesBuf->Setup(numLocalVerts, "VEC4", BufferData::UNSIGNED_SHORT, sizeof(uint16)*4);
		WeightsBuf->Setup(numLocalVerts, "VEC4", BufferData::UNSIGNED_BYTE, sizeof(uint32), /*InNormalized=*/ true);
	}

	// Prepare and build indices
	TArray<int> localIndices;
	localIndices.AddUninitialized(numLocalIndices);
	int* pIndex = localIndices.GetData();
	for (int i = 0; i < numLocalIndices; i++)
	{
		*pIndex++ = GetIndex(S.FirstIndex + i);
	}

	if (numLocalVerts <= 65536)
	{
		IndexBuf.Setup(numLocalIndices, "SCALAR", BufferData::UNSIGNED_SHORT, sizeof(uint16));
		for (int idx = 0; idx < numLocalIndices; idx++)
		{
			IndexBuf.Put<uint16>(indexRemap[localIndices[idx]]);
		}
	}
	else
	{
		IndexBuf.Setup(numLocalIndices, "SCALAR", BufferData::UNSIGNED_INT, sizeof(uint32));
		for (int idx = 0; idx < numLocalIndices; idx++)
		{
			IndexBuf.Put<uint32>(indexRemap[localIndices[idx]]);
		}
	}

	// Build reverse index map for fast lookup of vertex by its new index.
	// It maps new vertex index to old vertex index.
	TArray<int> revIndexMap;
	revIndexMap.AddUninitialized(numLocalVerts);
	for (int i = 0; i < indexRemap.Num(); i++)
	{
		int newIndex = indexRemap[i];
		if (newIndex != -1)
		{
			revIndexMap[newIndex] = i;
		}
	}

	// Build vertices
	for (int i = 0; i < numLocalVerts; i++)
	{
		int vertIndex = revIndexMap[i];
		const CMeshVertex& V = VERT(vertIndex);

		CVec3 Position = V.Position;

		CVec4 Normal, Tangent;
		Unpack(Normal, V.Normal);
		Unpack(Tangent, V.Tangent);
		// Unreal (and we are) using normal.w for computing binormal. glTF
		// uses tangent.w for that. Make this value exactly 1.0 of -1.0 to make glTF
		// validator happy.
	#if 0
		// There's some problem: V.Normal.W == 0x80 -> -1.008 instead of -1.0
		if (Normal.w > 1.001 || Normal.w < -1.001)
		{
			appError("%X -> %g\n", V.Normal.Data, Normal.w);
		}
	#endif
		Tangent.w = (Normal.w < 0) ? -1 : 1;

		TransformPosition(Position);
		TransformDirection(Normal);
		TransformDirection(Tangent);

		Normal.Normalize();
		Tangent.Normalize();

		// Fill buffers
		PositionBuf.Put(Position);
		NormalBuf.Put(Normal.xyz);
		TangentBuf.Put(Tangent);
		UVBuf[0]->Put(V.UV);
	}

	// Compute bounds for PositionBuf
	CVec3 Mins, Maxs;
	ComputeBounds((CVec3*)PositionBuf.Data, numLocalVerts, sizeof(CVec3), Mins, Maxs);
	char buf[256];
	appSprintf(ARRAY_ARG(buf), "[ %g, %g, %g ]", VECTOR_ARG(Mins));
	PositionBuf.BoundsMin = buf;
	appSprintf(ARRAY_ARG(buf), "[ %g, %g, %g ]", VECTOR_ARG(Maxs));
	PositionBuf.BoundsMax = buf;

	if (Context.IsSkeletal())
	{
		for (int i = 0; i < numLocalVerts; i++)
		{
			int vertIndex = revIndexMap[i];
			const CMeshVertex& V0 = VERT(vertIndex);
			const CSkelMeshVertex& V = static_cast<const CSkelMeshVertex&>(V0);

			int16 Bones[NUM_INFLUENCES];
			static_assert(NUM_INFLUENCES == 4, "Code designed for 4 influences");
			static_assert(sizeof(Bones) == sizeof(V.Bone), "Unexpected V.Bones size");
			memcpy(Bones, V.Bone, sizeof(Bones));
			for (int j = 0; j < NUM_INFLUENCES; j++)
			{
				// We have INDEX_NONE as list terminator, should replace with something else for glTF
				if (Bones[j] == INDEX_NONE)
				{
					Bones[j] = 0;
				}
			}

			BonesBuf->Put(*(uint64*)&Bones);
			WeightsBuf->Put(V.PackedWeights);
		}
	}

	// Secondary UVs
	for (int uvIndex = 1; uvIndex < Lod.NumTexCoords; uvIndex++)
	{
		BufferData* pBuf = UVBuf[uvIndex];
		const CMeshUVFloat* srcUV = Lod.ExtraUV[uvIndex-1];
		for (int i = 0; i < numLocalVerts; i++)
		{
			int vertIndex = revIndexMap[i];
			pBuf->Put(srcUV[vertIndex]);
		}
	}

	// Write primitive information to json
	Ar.Printf(
		"        {\n"
		"          \"attributes\" : {\n"
		"            \"POSITION\" : %d,\n"
		"            \"NORMAL\" : %d,\n"
		"            \"TANGENT\" : %d,\n",
		PositionBufIndex, NormalBufIndex, TangentBufIndex
	);
	if (Context.IsSkeletal())
	{
		Ar.Printf(
			"            \"JOINTS_0\" : %d,\n"
			"            \"WEIGHTS_0\" : %d,\n",
			BonesBufIndex, WeightsBufIndex
		);
	}
	for (int i = 0; i < Lod.NumTexCoords; i++)
	{
		Ar.Printf(
			"            \"TEXCOORD_%d\" : %d%s\n",
			i, UVBufIndex[i], i < (Lod.NumTexCoords-1) ? "," : ""
		);
	}

	Ar.Printf(
		"          },\n"
		"          \"indices\" : %d,\n"
		"          \"material\" : %d\n"
		"        }%s\n",
		IndexBufIndex, SectonIndex,
		SectonIndex < (Lod.Sections.Num()-1) ? "," : ""
	);

	unguard;
}
Ejemplo n.º 15
0
void USkelModel::Serialize(FArchive &Ar)
{
	guard(USkelModel::Serialize);

	assert(Ar.IsLoading);					// no saving ...
	Super::Serialize(Ar);

	// USkelModel data
	int nummeshes;
	int numjoints;
	int numframes;
	int numsequences;
	int numskins;
	int rootjoint;

	FVector					PosOffset;		// Offset of creature relative to base
	FRotator				RotOffset;		// Offset of creatures rotation

	TArray<RMesh>			meshes;
	TArray<RJoint>			joints;
	TArray<FRSkelAnimSeq>	AnimSeqs;		// Compressed animation data for sequence
	TArray<RAnimFrame>		frames;

	Ar << nummeshes << numjoints << numframes << numsequences << numskins << rootjoint;
	Ar << meshes << joints << AnimSeqs << frames << PosOffset << RotOffset;

	int modelIdx;
	// create all meshes first, then fill them (for better view order)
	for (modelIdx = 0; modelIdx < meshes.Num(); modelIdx++)
	{
		// create new USkeletalMesh
		// use "CreateClass()" instead of "new USkeletalMesh" to allow this object to be
		// placed in GObjObjects array and be browsable in a viewer
		USkeletalMesh *sm = static_cast<USkeletalMesh*>(CreateClass("SkeletalMesh"));
		char nameBuf[256];
		appSprintf(ARRAY_ARG(nameBuf), "%s_%d", Name, modelIdx);
		const char *name = appStrdupPool(nameBuf);
		Meshes.Add(sm);
		// setup UOnject
		sm->Name         = name;
		sm->Package      = Package;
		sm->PackageIndex = INDEX_NONE;		// not really exported
		sm->Outer        = NULL;
	}
	// create animation
	Anim = static_cast<UMeshAnimation*>(CreateClass("MeshAnimation"));
	Anim->Name         = Name;
	Anim->Package      = Package;
	Anim->PackageIndex = INDEX_NONE;		// not really exported
	Anim->Outer        = NULL;
	ConvertRuneAnimations(*Anim, joints, AnimSeqs);
	Anim->ConvertAnims();					//?? second conversion
	// get baseframe
	assert(strcmp(Anim->AnimSeqs[0].Name, "baseframe") == 0);
	const TArray<AnalogTrack> &BaseAnim = Anim->Moves[0].AnimTracks;
	// compute bone coordinates
	TArray<CCoords> BoneCoords;
	BuildSkeleton(BoneCoords, joints, BaseAnim);

	// setup meshes
	for (modelIdx = 0; modelIdx < meshes.Num(); modelIdx++)
	{
		int i, j;
		const RMesh  &src = meshes[modelIdx];
		USkeletalMesh *sm = Meshes[modelIdx];
		sm->Animation = Anim;
		// setup ULodMesh
		sm->RotOrigin = RotOffset;
		sm->MeshScale.Set(1, 1, 1);
		sm->MeshOrigin = PosOffset;
		// copy skeleton
		sm->RefSkeleton.Empty(joints.Num());
		for (i = 0; i < joints.Num(); i++)
		{
			const RJoint &J = joints[i];
			FMeshBone *B = new(sm->RefSkeleton) FMeshBone;
			B->Name = J.name;
			B->Flags = 0;
			B->ParentIndex = (J.parent > 0) ? J.parent : 0;		// -1 -> 0
			// copy bone orientations from base animation frame
			B->BonePos.Orientation = BaseAnim[i].KeyQuat[0];
			B->BonePos.Position    = BaseAnim[i].KeyPos[0];
		}
		// copy vertices
		int VertexCount = sm->VertexCount = src.verts.Num();
		sm->Points.Empty(VertexCount);
		for (i = 0; i < VertexCount; i++)
		{
			const RVertex &v1 = src.verts[i];
			FVector *V = new(sm->Points) FVector;
			// transform point from local bone space to model space
			BoneCoords[v1.joint1].UnTransformPoint(CVT(v1.point1), CVT(*V));
		}
		// copy triangles and create wedges
		// here we create 3 wedges for each triangle.
		// it is possible to reduce number of wedges by finding duplicates, but we don't
		// need it here ...
		int TrisCount = src.tris.Num();
		sm->Triangles.Empty(TrisCount);
		sm->Wedges.Empty(TrisCount * 3);
		int numMaterials = 0;		// should detect real material count
		for (i = 0; i < TrisCount; i++)
		{
			const RTriangle &tri = src.tris[i];
			// create triangle
			VTriangle *T = new(sm->Triangles) VTriangle;
			T->MatIndex = tri.polygroup;
			if (numMaterials <= tri.polygroup)
				numMaterials = tri.polygroup+1;
			// create wedges
			for (j = 0; j < 3; j++)
			{
				T->WedgeIndex[j] = sm->Wedges.Num();
				FMeshWedge *W = new(sm->Wedges) FMeshWedge;
				W->iVertex = tri.vIndex[j];
				W->TexUV   = tri.tex[j];
			}
			// reverse order of triangle vertices
			Exchange(T->WedgeIndex[0], T->WedgeIndex[1]);
		}
		// build influences
		for (i = 0; i < VertexCount; i++)
		{
			const RVertex &v1 = src.verts[i];
			FVertInfluence *Inf = new(sm->VertInfluences) FVertInfluence;
			Inf->PointIndex = i;
			Inf->BoneIndex  = v1.joint1;
			Inf->Weight     = v1.weight1;
			if (Inf->Weight != 1.0f)
			{
				// influence for 2nd bone
				Inf = new(sm->VertInfluences) FVertInfluence;
				Inf->PointIndex = i;
				Inf->BoneIndex  = v1.joint2;
				Inf->Weight     = 1.0f - v1.weight1;
			}
		}
		// create materials
		for (i = 0; i < numMaterials; i++)
		{
			const char *texName = src.PolyGroupSkinNames[i];
			FMeshMaterial *M1 = new(sm->Materials) FMeshMaterial;
			M1->PolyFlags    = src.GroupFlags[i];
			M1->TextureIndex = sm->Textures.Num();
			if (strcmp(texName, "None") == 0)
			{
				// texture should be set from script
				sm->Textures.Add(NULL);
				continue;
			}
			// find texture in object's package
			int texExportIdx = Package->FindExport(texName);
			if (texExportIdx == INDEX_NONE)
			{
				appPrintf("ERROR: unable to find export \"%s\" for mesh \"%s\" (%d)\n",
					texName, Name, modelIdx);
				continue;
			}
			// load and remember texture
			UMaterial *Tex = static_cast<UMaterial*>(Package->CreateExport(texExportIdx));
			sm->Textures.Add(Tex);
		}
		// setup UPrimitive properties using 1st animation frame
		// note: this->BoundingBox and this->BoundingSphere are null
		const RAnimFrame &F = frames[0];
		assert(strcmp(AnimSeqs[0].Name, "baseframe") == 0 && AnimSeqs[0].StartFrame == 0);
		CVec3 mins, maxs;
		sm->BoundingBox = F.bounds;
		mins = CVT(F.bounds.Min);
		maxs = CVT(F.bounds.Max);
		CVec3 &center = CVT(sm->BoundingSphere);
		for (i = 0; i < 3; i++)
			center[i] = (mins[i] + maxs[i]) / 2;
		sm->BoundingSphere.R = VectorDistance(center, mins);

		// create CSkeletalMesh
		sm->ConvertMesh();
	}

	unguard;
}
Ejemplo n.º 16
0
void LoadQ1BspFile()
{
	guard(LoadQ1BspFile);

	dBsp1Hdr_t *header = (dBsp1Hdr_t *) bspfile.file;
	lumps = header->lumps;

#if !LITTLE_ENDIAN
	// swap the header
	for (int i = 0; i < sizeof(dBsp1Hdr_t) / 4; i++)
		((int *)bspfile.file)[i] = LittleLong(((int *)bspfile.file)[i]);
#endif

	if (header->version == BSP1_VERSION)
		bspfile.type = map_q1;
	else
		bspfile.type = map_hl;
	Com_DPrintf("Loading %s bsp %s\n", bspfile.type == map_q1 ? "Q1" : "HL", *bspfile.Name);

#define C(num,field,count,type) \
	bspfile.count = CheckLump(dBsp1Hdr_t::LUMP_##num, (void**)&bspfile.field, sizeof(type))
	C(LIGHTING, lighting, lightDataSize, byte);
	C(VERTEXES, vertexes2, numVertexes, CVec3);
	C(PLANES, planes2, numPlanes, dBsp2Plane_t);
	C(LEAFS, leafs1, numLeafs, dBsp1Leaf_t);
	C(NODES, nodes1, numNodes, dBsp1Node_t);
	C(TEXINFO, texinfo1, numTexinfo, dBsp1Texinfo_t);
	C(FACES, faces2, numFaces, dBspFace_t);
	C(MARKSURFACES, leaffaces2, numLeaffaces, unsigned short);
	C(SURFEDGES, surfedges, numSurfedges, int);
	C(EDGES, edges, numEdges, dEdge_t);
	C(MODELS, models1, numModels, dBsp1Model_t);

	// load miptex lump
	bspfile.miptex1 = (dBsp1MiptexLump_t*)(bspfile.file + lumps[dBsp1Hdr_t::LUMP_TEXTURES].fileofs);
#if !LITTLE_ENDIAN
	//!! swap miptex1 lump
#endif
	for (int miptex = 0; miptex < bspfile.miptex1->nummiptex; miptex++)
	{
		int offset = bspfile.miptex1->dataofs[miptex];
		if (offset == -1)
			continue;
		dBsp1Miptex_t *tex = (dBsp1Miptex_t*)( (byte*)bspfile.miptex1 + offset );
		// texture with data, but without name -- create default name
		if (!tex->name[0])
			appSprintf(ARRAY_ARG(tex->name), "unnamed#%d", miptex);
	}

#if !LITTLE_ENDIAN
	// swap everything
	SwapQ1BspFile(&bspfile);
#endif
	ProcessQ1BspFile(&bspfile);

	// load visinfo
	byte *vis;
	int visDataSize = CheckLump(dBsp1Hdr_t::LUMP_VISIBILITY, (void**)&vis, 1);
	LoadQ1Vis(&bspfile, vis, visDataSize);
	// load entstring after all: we may require to change something
	char *entString = (char*)header + header->lumps[dBsp1Hdr_t::LUMP_ENTITIES].fileofs;
	bspfile.entStr = entString;

#undef C
	unguard;
}
Ejemplo n.º 17
0
//
// Constructor.
//
UXViewport::UXViewport()
:	UViewport()
,	ViewportStatus( X_ViewportOpening )
{
	guard(UXViewport::UXViewport);

	// Open the display.
	XDisplay = XOpenDisplay(0);
	if (!XDisplay)
		appErrorf( TEXT("Can't open X server display.  XViewport requires X windows.") );

	// Create the default window.
	XSetWindowAttributes swa;
	swa.colormap = DefaultColormap(XDisplay, DefaultScreen(XDisplay));
	swa.border_pixel = 0;
	swa.override_redirect = True;
//	swa.override_redirect = False;
	swa.event_mask = ExposureMask | StructureNotifyMask | 
		KeyPressMask | KeyReleaseMask | ButtonPressMask | 
		ButtonReleaseMask | ButtonMotionMask | ResizeRedirectMask |
		PointerMotionMask | FocusChangeMask;
	XWindow = XCreateWindow(
		XDisplay,
		DefaultRootWindow(XDisplay),
		0, 0,
		640, 480,
		0,
		DefaultDepth(XDisplay, DefaultScreen(XDisplay)),
		InputOutput, DefaultVisual(XDisplay, DefaultScreen(XDisplay)),
		CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect, &swa
	);

	TCHAR WindowName[80];
	appSprintf( WindowName, TEXT("Unreal Tournament") );
	XStoreName( XDisplay, XWindow, WindowName );

	XMapWindow(XDisplay, XWindow);
	Mapped = True;

	// Set color bytes based on screen resolution.
	switch( DefaultDepth( XDisplay, 0 ) )
	{
		case 8:
			ColorBytes  = 2;
			break;
		case 16:
			ColorBytes  = 2;
			Caps       |= CC_RGB565;
			break;
		case 24:
			ColorBytes  = 4;
			break;
		case 32: 
			ColorBytes  = 4;
			break;
		default: 
			ColorBytes  = 2; 
			Caps       |= CC_RGB565;
			break;
	}

	// Zero out Keysym map.
	for (INT i=0; i<65536; i++)
		KeysymMap[i] = 0;
		
	// Remap important keys.

	// TTY Functions.
	KeysymMap[XK_BackSpace]	= IK_Backspace;
	KeysymMap[XK_Tab]		= IK_Tab;
	KeysymMap[XK_Return]	= IK_Enter;
	KeysymMap[XK_Pause]		= IK_Pause;
	KeysymMap[XK_Escape]	= IK_Escape;
	KeysymMap[XK_Delete]	= IK_Delete;

	// Modifiers.
	KeysymMap[XK_Shift_L]	= IK_LShift;
	KeysymMap[XK_Shift_R]	= IK_RShift;
	KeysymMap[XK_Control_L]	= IK_LControl;
	KeysymMap[XK_Control_R]	= IK_RControl;
	KeysymMap[XK_Meta_L]	= IK_Alt;
	KeysymMap[XK_Meta_R]	= IK_Alt;
	KeysymMap[XK_Alt_L]		= IK_Alt;
	KeysymMap[XK_Alt_R]		= IK_Alt;
	
	// Special remaps.
	KeysymMap[XK_grave]		= IK_Tilde;

	// Misc function keys.
	KeysymMap[XK_F1]		= IK_F1;
	KeysymMap[XK_F2]		= IK_F2;
	KeysymMap[XK_F3]		= IK_F3;
	KeysymMap[XK_F4]		= IK_F4;
	KeysymMap[XK_F5]		= IK_F5;
	KeysymMap[XK_F6]		= IK_F6;
	KeysymMap[XK_F7]		= IK_F7;
	KeysymMap[XK_F8]		= IK_F8;
	KeysymMap[XK_F9]		= IK_F9;
	KeysymMap[XK_F10]		= IK_F10;
	KeysymMap[XK_F11]		= IK_F11;
	KeysymMap[XK_F12]		= IK_F12;

	// Cursor control and motion.
	KeysymMap[XK_Home]		= IK_Home;
	KeysymMap[XK_Left]		= IK_Left;
	KeysymMap[XK_Up]		= IK_Up;
	KeysymMap[XK_Right]		= IK_Right;
	KeysymMap[XK_Down]		= IK_Down;
	KeysymMap[XK_Page_Up]	= IK_PageUp;
	KeysymMap[XK_Page_Down]	= IK_PageDown;
	KeysymMap[XK_End]		= IK_End;

	// Keypad functions and numbers.
	KeysymMap[XK_KP_Enter]		= IK_Enter;
	KeysymMap[XK_KP_0]			= IK_NumPad0;
	KeysymMap[XK_KP_1]			= IK_NumPad1;
	KeysymMap[XK_KP_2]			= IK_NumPad2;
	KeysymMap[XK_KP_3]			= IK_NumPad3;
	KeysymMap[XK_KP_4]			= IK_NumPad4;
	KeysymMap[XK_KP_5]			= IK_NumPad5;
	KeysymMap[XK_KP_6]			= IK_NumPad6;
	KeysymMap[XK_KP_7]			= IK_NumPad7;
	KeysymMap[XK_KP_8]			= IK_NumPad8;
	KeysymMap[XK_KP_9]			= IK_NumPad9;
	KeysymMap[XK_KP_Multiply]	= IK_GreyStar;
	KeysymMap[XK_KP_Add]		= IK_GreyPlus;
	KeysymMap[XK_KP_Separator]	= IK_Separator;
	KeysymMap[XK_KP_Subtract]	= IK_GreyMinus;
	KeysymMap[XK_KP_Decimal]	= IK_NumPadPeriod;
	KeysymMap[XK_KP_Divide]		= IK_GreySlash;

	// Other
	KeysymMap[XK_minus]			= IK_Minus;
	KeysymMap[XK_equal]			= IK_Equals;
	
	// Zero out ShiftMask map.
	for (i=0; i<256; i++)
		ShiftMaskMap[i] = 0;

	// ShiftMask map.
	ShiftMaskMap['1']			= '!';
	ShiftMaskMap['2']			= '@';
	ShiftMaskMap['3']			= '#';
	ShiftMaskMap['4']			= '$';
	ShiftMaskMap['5']			= '%';
	ShiftMaskMap['6']			= '^';
	ShiftMaskMap['7']			= '&';
	ShiftMaskMap['8']			= '*';
	ShiftMaskMap['9']			= '(';
	ShiftMaskMap['0']			= ')';
	ShiftMaskMap['-']			= '_';
	ShiftMaskMap['=']			= '+';
	ShiftMaskMap['[']			= '{';
	ShiftMaskMap[']']			= '}';
	ShiftMaskMap['\\']			= '|';
	ShiftMaskMap[';']			= ':';
	ShiftMaskMap['\'']			= '\"';
	ShiftMaskMap[',']			= '<';
	ShiftMaskMap['.']			= '>';
	ShiftMaskMap['/']			= '?';

	// WM_CHAR allowables.
	for (i=0; i<256; i++)
		WMCharMap[i] = 0;
	for (i='A'; i<='Z'; i++)
		WMCharMap[i] = 1;
	for (i='a'; i<='z'; i++)
		WMCharMap[i] = 1;
	WMCharMap[IK_Backspace]		= 1;
	WMCharMap[IK_Space]			= 1;
	WMCharMap[IK_Tab]			= 1;
	WMCharMap[IK_Enter]			= 1;
	WMCharMap['1']				= 1;
	WMCharMap['2']				= 1;
	WMCharMap['3']				= 1;
	WMCharMap['4']				= 1;
	WMCharMap['5']				= 1;
	WMCharMap['6']				= 1;
	WMCharMap['7']				= 1;
	WMCharMap['8']				= 1;
	WMCharMap['9']				= 1;
	WMCharMap['0']				= 1;
	WMCharMap['-']				= 1;
	WMCharMap['=']				= 1;
	WMCharMap['[']				= 1;
	WMCharMap[']']				= 1;
	WMCharMap['\\']				= 1;
	WMCharMap[';']				= 1;
	WMCharMap['\'']				= 1;
	WMCharMap[',']				= 1;
	WMCharMap['.']				= 1;
	WMCharMap['/']				= 1;
	WMCharMap['!']				= 1;
	WMCharMap['@']				= 1;
	WMCharMap['#']				= 1;
	WMCharMap['$']				= 1;
	WMCharMap['%']				= 1;
	WMCharMap['^']				= 1;
	WMCharMap['&']				= 1;
	WMCharMap['*']				= 1;
	WMCharMap['(']				= 1;
	WMCharMap[')']				= 1;
	WMCharMap['_']				= 1;
	WMCharMap['+']				= 1;
	WMCharMap['{']				= 1;
	WMCharMap['}']				= 1;
	WMCharMap['|']				= 1;
	WMCharMap[':']				= 1;
	WMCharMap['\"']				= 1;
	WMCharMap['<']				= 1;
	WMCharMap['>']				= 1;
	WMCharMap['?']				= 1;
	
	// Zero out KeyRepeat map.
	for (i=0; i<256; i++)
		KeyRepeatMap[i] = 0;

	// Remember pointer settings.
	XGetPointerControl(XDisplay, &MouseAccel_N, &MouseAccel_D, &MouseThreshold);

	debugf( TEXT("Created and initialized a new X viewport.") );

	unguard;
}
Ejemplo n.º 18
0
char *Sys_ConsoleInput()
{
	guard(Sys_ConsoleInput);

#if !NO_DEDICATED
	if (console_drawInput)
	{
		// display input line
		Win32Log.WriteChar(']');
		if (console_textlen)
		{
			console_text[console_textlen] = 0;
			Win32Log.WriteStr(console_text);
		}
		console_drawInput = false;
	}

	while (true)
	{
		DWORD	numevents, numread;
		GetNumberOfConsoleInputEvents(hConInput, &numevents);
		if (numevents <= 0) break;

		INPUT_RECORD rec;
		ReadConsoleInput(hConInput, &rec, 1, &numread);
		if (rec.EventType == KEY_EVENT && rec.Event.KeyEvent.bKeyDown)
		{
			int ch = rec.Event.KeyEvent.uChar.AsciiChar;
			switch (ch)
			{
				case '\r':		// ENTER
					Win32Log.WriteStr("\r\n");
					console_drawInput = true;
					if (console_textlen)
					{
						console_textlen = 0;
						return console_text;
					}
					break;

				case '\b':		// BACKSPACE
					if (console_textlen)
					{
						console_text[--console_textlen] = 0;
						Win32Log.WriteStr("\b \b");
					}
					break;

				case '\t':		// TAB
					{
						appSprintf(ARRAY_ARG(editLine), "]%s", console_text);
						CompleteCommand();
						const char *s = editLine;
						if (s[0] == ']') s++;
						if (s[0] == '/') s++;
						int len = strlen(s);
						if (len > 0)
						{
							console_textlen = min(len, sizeof(console_text)-2);
							appStrncpyz(console_text, s, console_textlen+1);
							Win32Log.EraseInput();
							console_drawInput = true;	// next time ...
						}
					}
					break;

				case '\x1B':	// ESC
					Win32Log.EraseInput();
					console_textlen = 0;
					console_text[0] = 0;
					break;

				default:
					if (ch >= ' ')
					{
						if (console_textlen < sizeof(console_text)-2)
						{
							Win32Log.WriteChar(ch);
							console_text[console_textlen++] = ch;
							console_text[console_textlen] = 0;
						}
					}
//					else
//						appPrintf("%2X\n",ch);
					break;
			}
		}
	}
#endif
	return NULL;

	unguard;
}