static bool CreateDemoFileSplit(udtVMLinearAllocator& tempAllocator, udtContext& context, udtStream& file, const char* filePath, const char* outputFolderPath, u32 index, u32 startOffset, u32 endOffset) { if(endOffset <= startOffset) { return false; } if(file.Seek((s32)startOffset, udtSeekOrigin::Start) != 0) { return false; } const udtProtocol::Id protocol = udtGetProtocolByFilePath(filePath); if(protocol == udtProtocol::Invalid) { return false; } udtVMScopedStackAllocator scopedTempAllocator(tempAllocator); const udtString filePathString = udtString::NewConstRef(filePath); udtString fileName; if(!udtPath::GetFileNameWithoutExtension(fileName, tempAllocator, filePathString)) { fileName = udtString::NewConstRef("NEW_UDT_SPLIT_DEMO"); } udtString outputFilePathStart; if(outputFolderPath == NULL) { udtString inputFolderPath; udtPath::GetFolderPath(inputFolderPath, tempAllocator, filePathString); udtPath::Combine(outputFilePathStart, tempAllocator, inputFolderPath, fileName); } else { udtPath::Combine(outputFilePathStart, tempAllocator, udtString::NewConstRef(outputFolderPath), fileName); } char* newFilePath = AllocateSpaceForString(tempAllocator, UDT_MAX_PATH_LENGTH); sprintf(newFilePath, "%s_SPLIT_%u%s", outputFilePathStart.String, index + 1, udtGetFileExtensionByProtocol(protocol)); context.LogInfo("Writing demo %s...", newFilePath); udtFileStream outputFile; if(!outputFile.Open(newFilePath, udtFileOpenMode::Write)) { context.LogError("Could not open file"); return false; } const bool success = CopyFileRange(file, outputFile, tempAllocator, startOffset, endOffset); if(!success) { context.LogError("File copy failed"); } return success; }
UDT_API(s32) udtSplitDemoFile(udtParserContext* context, const udtParseArg* info, const char* demoFilePath) { if(context == NULL || info == NULL || demoFilePath == NULL || !HasValidOutputOption(*info)) { return (s32)udtErrorCode::InvalidArgument; } const udtProtocol::Id protocol = udtGetProtocolByFilePath(demoFilePath); if(protocol == udtProtocol::Invalid) { return (s32)udtErrorCode::InvalidArgument; } context->ResetForNextDemo(false); if(!context->Context.SetCallbacks(info->MessageCb, info->ProgressCb, info->ProgressContext)) { return (s32)udtErrorCode::OperationFailed; } udtFileStream file; if(!file.Open(demoFilePath, udtFileOpenMode::Read)) { return (s32)udtErrorCode::OperationFailed; } if(!context->Parser.Init(&context->Context, protocol, protocol)) { return (s32)udtErrorCode::OperationFailed; } context->Parser.SetFilePath(demoFilePath); // TODO: Move this to api_helpers.cpp and implement it the same way Cut by Pattern is? udtParserPlugInSplitter plugIn; plugIn.Init(1, context->PlugInTempAllocator); context->Parser.AddPlugIn(&plugIn); if(!RunParser(context->Parser, file, info->CancelOperation)) { return (s32)udtErrorCode::OperationFailed; } if(plugIn.GamestateFileOffsets.GetSize() <= 1) { return (s32)udtErrorCode::None; } udtVMLinearAllocator& tempAllocator = context->Parser._tempAllocator; tempAllocator.Clear(); if(!CreateDemoFileSplit(tempAllocator, context->Context, file, demoFilePath, info->OutputFolderPath, &plugIn.GamestateFileOffsets[0], plugIn.GamestateFileOffsets.GetSize())) { return (s32)udtErrorCode::OperationFailed; } return (s32)udtErrorCode::None; }
int udt_main(int argc, char** argv) { if(argc == 1) { PrintHelp(); return 0; } bool fileMode = false; const char* const inputPath = argv[argc - 1]; if(udtFileStream::Exists(inputPath) && udtPath::HasValidDemoFileExtension(inputPath)) { fileMode = true; } else if(!IsValidDirectory(inputPath)) { fprintf(stderr, "Invalid file/folder path.\n"); return 1; } bool recursive = false; Config config; for(int i = 1; i < argc - 1; ++i) { s32 localMaxThreads = 1; s32 localProtocol = (s32)udtProtocol::Invalid; const udtString arg = udtString::NewConstRef(argv[i]); if(udtString::StartsWith(arg, "-p=") && arg.GetLength() >= 4 && StringParseInt(localProtocol, arg.GetPtr() + 3)) { if(localProtocol == 68) { config.OutputProtocol = udtProtocol::Dm68; } else if(localProtocol == 91) { config.OutputProtocol = udtProtocol::Dm91; } } else if(udtString::Equals(arg, "-r")) { recursive = true; } else if(udtString::StartsWith(arg, "-o=") && arg.GetLength() >= 4 && IsValidDirectory(argv[i] + 3)) { config.CustomOutputFolder = argv[i] + 3; } else if(udtString::StartsWith(arg, "-t=") && arg.GetLength() >= 4 && StringParseInt(localMaxThreads, arg.GetPtr() + 3) && localMaxThreads >= 1 && localMaxThreads <= 16) { config.MaxThreadCount = (u32)localMaxThreads; } } if(config.OutputProtocol == udtProtocol::Invalid) { fprintf(stderr, "Invalid or unspecified output protocol number.\n"); return 1; } if(fileMode) { if(!IsValidConversion((udtProtocol::Id)udtGetProtocolByFilePath(inputPath), config.OutputProtocol)) { fprintf(stderr, "Unsupported conversion.\n"); return 1; } udtFileInfo fileInfo; fileInfo.Name = udtString::NewNull(); fileInfo.Path = udtString::NewConstRef(inputPath); fileInfo.Size = 0; return ConvertMultipleDemos(&fileInfo, 1, config) ? 0 : 1; } udtFileListQuery query; query.InitAllocators(64); query.FileFilter = &KeepOnlyCompatibleDemoFiles; query.FolderPath = udtString::NewConstRef(inputPath); query.Recursive = recursive; query.UserData = &config; GetDirectoryFileList(query); if(query.Files.IsEmpty()) { fprintf(stderr, "No compatible demo file found.\n"); return 1; } return ConvertMultipleDemos(query.Files.GetStartAddress(), query.Files.GetSize(), config) ? 0 : 1; }
static bool KeepOnlyCompatibleDemoFiles(const char* name, u64 /*size*/, void* userData) { const Config& config = *(const Config*)userData; return IsValidConversion((udtProtocol::Id)udtGetProtocolByFilePath(name), config.OutputProtocol); }
bool ProcessDemoFile(const char* filePath) { for(u32 i = 0; i < ID_MAX_CLIENTS; ++i) { _players[i].Name.clear(); _players[i].Valid = false; } FileReader reader; if(!reader.Open(filePath)) { return false; } udtCuContext* const cuContext = _cuContext; const u32 protocol = udtGetProtocolByFilePath(filePath); const s32 firstPlayerIdx = udtGetIdConfigStringIndex((u32)udtConfigStringIndex::FirstPlayer, protocol); if(firstPlayerIdx < 0) { PrintError("Failed to get the first index of player config strings"); return false; } _protocol = (udtProtocol::Id)protocol; _protocolFirstPlayerCsIdx = firstPlayerIdx; s32 errorCode = udtCuStartParsing(cuContext, protocol); if(errorCode != udtErrorCode::None) { PrintError("udtCuStartParsing failed: %s", udtGetErrorCodeString(errorCode)); return false; } udtCuMessageInput input; udtCuMessageOutput output; u32 continueParsing = 0; for(;;) { if(!reader.Read(&input.MessageSequence, 4)) { PrintWarning("Demo is truncated"); return true; } if(!reader.Read(&input.BufferByteCount, 4)) { PrintWarning("Demo is truncated"); return true; } if(input.MessageSequence == -1 && input.BufferByteCount == u32(-1)) { // End of demo file. break; } if(input.BufferByteCount > ID_MAX_MSG_LENGTH) { PrintError("Corrupt input: the buffer length exceeds the maximum allowed"); return false; } if(!reader.Read(_inMsgData, input.BufferByteCount)) { PrintWarning("Demo is truncated"); return true; } input.Buffer = _inMsgData; errorCode = udtCuParseMessage(cuContext, &output, &continueParsing, &input); if(errorCode != udtErrorCode::None) { PrintError("udtCuParseMessage failed: %s", udtGetErrorCodeString(errorCode)); return false; } if(continueParsing == 0) { break; } AnalyzeMessage(output); } return true; }
UDT_API(s32) udtCutDemoFileByTime(udtParserContext* context, const udtParseArg* info, const udtCutByTimeArg* cutInfo, const char* demoFilePath) { if(context == NULL || info == NULL || demoFilePath == NULL || cutInfo == NULL || !IsValid(*cutInfo)) { return (s32)udtErrorCode::InvalidArgument; } const udtProtocol::Id protocol = udtGetProtocolByFilePath(demoFilePath); if(protocol == udtProtocol::Invalid) { return (s32)udtErrorCode::OperationFailed; } context->ResetForNextDemo(false); if(!context->Context.SetCallbacks(info->MessageCb, info->ProgressCb, info->ProgressContext)) { return (s32)udtErrorCode::OperationFailed; } udtFileStream file; if(!file.Open(demoFilePath, udtFileOpenMode::Read)) { return (s32)udtErrorCode::OperationFailed; } if(info->FileOffset > 0 && file.Seek((s32)info->FileOffset, udtSeekOrigin::Start) != 0) { return (s32)udtErrorCode::OperationFailed; } if(!context->Parser.Init(&context->Context, protocol, protocol, info->GameStateIndex)) { return (s32)udtErrorCode::OperationFailed; } CallbackCutDemoFileStreamCreationInfo streamInfo; streamInfo.OutputFolderPath = info->OutputFolderPath; context->Parser.SetFilePath(demoFilePath); for(u32 i = 0; i < cutInfo->CutCount; ++i) { const udtCut& cut = cutInfo->Cuts[i]; if(cut.StartTimeMs < cut.EndTimeMs) { context->Parser.AddCut( info->GameStateIndex, cut.StartTimeMs, cut.EndTimeMs, &CallbackCutDemoFileStreamCreation, NULL, &streamInfo); } } context->Context.LogInfo("Processing for a timed cut: %s", demoFilePath); if(!RunParser(context->Parser, file, info->CancelOperation)) { return (s32)udtErrorCode::OperationFailed; } return (s32)udtErrorCode::None; }