Если называть ЯВУ все что не является ассемблером(например си): на ЯВУ есть свой кайф при программировании. Например если твой код не зависит от внешних библиотек/фреймворков это тоже кайф.
А вот тут Intel был ни при чём. Это был типичный ассемблерный ночной баг. Нашёл, исправил. Мне одному не дают спать Вулкан и ассемблер по ночам?
Нет я тоже по всем ночам не сплю - всё жду когда вы мне, под мой музейный экспонат процессора, код на MASM64 - для OpenGL напишите.
Раз обещал - нехорошо заставлять ждать. Вообще я хотел сначала закончить парсер obj -> VBO, а потом перенести его в OpenGL-рамку, но если Вас терзает бессонница, то придётся отвлечься
GRAFik, у вас есть хотябы 1 проект, который вы сами написали и выложили на васме, пусть даже при помощи ллм?
Я бы не отвлекался и на этого сетевого хомяка болт бы положил Он же вам деньги не платит? Какое имеет моральное право критиковать и отвлекать от творчества/лезть не в свое дело?
Application, что за менторская манера общения? Модераторская власть что-ли отпечаток накладывает? И на брудершафт мы с вами, вроде, не пили. Так, что повежливее нужно быть с своими зарегистрированными пользователями/форумчанами - их у вас и так, как кот наплакал...
Здесь наши интересы совпадают: мой движок OpenGL основан на технологиях прошлого века (OpenGL 1.0). Работать с реальными 3D-моделями он просто не способен. Поэтому мне нужно прикрутить к нему парсер VAO/VBO/EBO. Если при этом он кому-то будет полезен, то я только буду рад. Vulkan - штука нестабильная и требует наличия библиотеки. OpenGL под Windows запускается из коробки. У самурая нет цели, есть только путь. Но я иду в сторону своего IFC-ридера, а возможно, и редактора. Мы живём в интересное время. Сейчас я один могу делать столько же, сколько 30 лет назад делал весь Autodesk или Graphisoft (в плане разработки, не маркетинга). Поэтому мне не жалко написать один модуль, чтобы человеку стало весело. Я не гонюсь за авторским правом, потому что всё это придумано не мной и давно написано, я учусь сам и показываю другим людям.
Альтернативное времяпрепровождение - это прикольные видосики в телефоне. В лучшем случае просто тупые, в худшем - пропагандистские. Я выбрал другой путь. Так узнаю много нового (технологически нового) и держу мозг в тонусе. И я не знаю, где пригодятся мои знания в будущем. Уже сейчас я держу в руках связку WinAPI-Vulkan-GLSL, причём на чистом ассемблере, без третьих библиотек. Считаю матрицы преобразований с помощью AVX и FMA. В реальной жизни это мне никогда не пригодится. Почти наверное. Никто даже не знает, что я это умею. А что если я по приколу напишу свой мини-Revit, который будет соответствовать ГОСТам? Тупо чтобы потроллить Нанософт и Аскон? Задача по линии VK и OpenGL сейчас единая - мне нужен парсер obj -> VBO, причём не просто какой-нибудь, а хороший, годный - построенный на векторных инструкциях. Когда будет парсер, будет и визуализация
ml64, мне кажется, что на картинке девочка-подросток с большой грудью или у «Москвича» размер «Чайки»? размеры«Чайка»«Москвич 408»длина5600 мм4090 ммширина2000 мм1550 ммвысота1620 мм1480 ммколёсная база3250 мм2400 мм
А мне дед старые фотографии показывал, когда были в моде "инвалидки", как в старой комедии "самогонщики". Вот такую бы подкрасить, подсветить и с такой девушкой сфотографировать.
Точно, именно мотоколяска. У вас нет такой в вашей кодовой базе ? Тогда сразу оформляем спецзаказ, с полной предоплатой. Девушки только могут подвести. Они на такой позор могут не подписаться - ни за какие деньги.
GRAFik, «Операция Ы» — мотоколяска СМЗ С-3А «Кавказская пленница» — немецкий Adler Triumpf Junior 1937 года
Может такое проще на python делать? В асм передавать сконвертированные файлы. Код (Python): import numpy as np import struct from pathlib import Path from typing import List, Tuple, Dict, Optional from collections import defaultdict import argparse import sys class OBJParser: """ Чистый парсер OBJ в VBO/EBO без привязки к рендерингу Использует векторизованные операции через NumPy """ def __init__(self): # Сырые данные self.vertices = [] # v x y z self.normals = [] # vn x y z self.texcoords = [] # vt u v self.faces = [] # f v/vt/vn ... # Результат self.vbo_data = None # Interleaved vertex data [x,y,z,nx,ny,nz,u,v] self.ebo_data = None # Indices self.vertex_count = 0 self.index_count = 0 def parse(self, filepath: str) -> 'OBJParser': """ Парсинг OBJ файла """ self.vertices = [] self.normals = [] self.texcoords = [] self.faces = [] with open(filepath, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue parts = line.split() cmd = parts[0] if cmd == 'v': # v x y z self.vertices.append([float(parts[1]), float(parts[2]), float(parts[3])]) elif cmd == 'vn': # vn x y z self.normals.append([float(parts[1]), float(parts[2]), float(parts[3])]) elif cmd == 'vt': # vt u v u = float(parts[1]) v = float(parts[2]) if len(parts) > 2 else 0.0 self.texcoords.append([u, v]) elif cmd == 'f': # f v1/vt1/vn1 v2/vt2/vn2 ... face = [] for i in range(1, len(parts)): indices = parts[i].split('/') v_idx = int(indices[0]) - 1 vt_idx = int(indices[1]) - 1 if len(indices) > 1 and indices[1] else -1 vn_idx = int(indices[2]) - 1 if len(indices) > 2 and indices[2] else -1 face.append((v_idx, vt_idx, vn_idx)) self.faces.append(face) # Конвертируем в NumPy для векторизации self.vertices = np.array(self.vertices, dtype=np.float32) self.normals = np.array(self.normals, dtype=np.float32) if self.normals else None self.texcoords = np.array(self.texcoords, dtype=np.float32) if self.texcoords else None return self def compute_normals(self) -> np.ndarray: """ Векторизованное вычисление нормалей через NumPy """ if self.normals is not None: return self.normals print("Computing normals...") # Собираем треугольники triangles = [] for face in self.faces: if len(face) >= 3: # Триангуляция веером for i in range(1, len(face) - 1): v0 = face[0][0] v1 = face[i][0] v2 = face[i + 1][0] if v0 >= 0 and v1 >= 0 and v2 >= 0: triangles.append((v0, v1, v2)) if not triangles: raise ValueError("No triangles found in OBJ") triangles = np.array(triangles, dtype=np.int32) # Векторизованное вычисление v0 = self.vertices[triangles[:, 0]] v1 = self.vertices[triangles[:, 1]] v2 = self.vertices[triangles[:, 2]] edge1 = v1 - v0 edge2 = v2 - v0 normals = np.cross(edge1, edge2) # Нормализация lengths = np.sqrt(np.sum(normals * normals, axis=1, keepdims=True)) lengths = np.where(lengths == 0, 1, lengths) normals = normals / lengths # Агрегация по вершинам vertex_normals = np.zeros_like(self.vertices) np.add.at(vertex_normals, triangles[:, 0], normals) np.add.at(vertex_normals, triangles[:, 1], normals) np.add.at(vertex_normals, triangles[:, 2], normals) # Нормализация итоговых нормалей lengths = np.sqrt(np.sum(vertex_normals * vertex_normals, axis=1, keepdims=True)) lengths = np.where(lengths == 0, 1, lengths) vertex_normals = vertex_normals / lengths return vertex_normals def build_vbo_ebo(self, deduplicate: bool = True) -> Tuple[np.ndarray, np.ndarray]: """ Строит VBO и EBO Args: deduplicate: Удалять ли дублирующиеся вершины Returns: (vbo_data, ebo_data) - интерливированные данные и индексы """ print(f"Building VBO/EBO (deduplicate={deduplicate})...") normals = self.compute_normals() # Подготовка UV if self.texcoords is None: texcoords = np.zeros((len(self.vertices), 2), dtype=np.float32) else: texcoords = self.texcoords if not deduplicate: # Простой вариант: все вершины как есть vertex_count = len(self.vertices) vbo_data = np.zeros((vertex_count, 8), dtype=np.float32) vbo_data[:, 0:3] = self.vertices vbo_data[:, 3:6] = normals vbo_data[:, 6:8] = texcoords # Индексы - просто последовательность indices = [] for face in self.faces: for i in range(1, len(face) - 1): indices.append(face[0][0]) indices.append(face[i][0]) indices.append(face[i + 1][0]) ebo_data = np.array(indices, dtype=np.uint32) else: # С дедупликацией через хеширование unique_vertices = [] index_map = {} indices = [] for face in self.faces: for i in range(1, len(face) - 1): v0, v1, v2 = face[0], face[i], face[i + 1] for v in (v0, v1, v2): v_idx, vt_idx, vn_idx = v pos = self.vertices[v_idx] norm = normals[v_idx] if vn_idx == -1 else self.normals[vn_idx] tex = texcoords[vt_idx] if vt_idx != -1 else texcoords[v_idx] # Хеш с округлением для точности key = ( round(pos[0], 6), round(pos[1], 6), round(pos[2], 6), round(norm[0], 6), round(norm[1], 6), round(norm[2], 6), round(tex[0], 6), round(tex[1], 6) ) if key not in index_map: index_map[key] = len(unique_vertices) unique_vertices.append([ pos[0], pos[1], pos[2], norm[0], norm[1], norm[2], tex[0], tex[1] ]) indices.append(index_map[key]) vbo_data = np.array(unique_vertices, dtype=np.float32) ebo_data = np.array(indices, dtype=np.uint32) self.vbo_data = vbo_data self.ebo_data = ebo_data self.vertex_count = len(vbo_data) self.index_count = len(ebo_data) return vbo_data, ebo_data def save_binary(self, output_path: str, format: str = 'vbo'): """ Сохраняет в бинарный файл Args: output_path: Путь к выходному файлу format: 'vbo' - стандартный, 'obj' - с заголовком """ if self.vbo_data is None or self.ebo_data is None: self.build_vbo_ebo() with open(output_path, 'wb') as f: if format == 'vbo': # Формат: [vertex_count, index_count, stride] + данные f.write(struct.pack('III', self.vertex_count, self.index_count, 8 # 8 float на вершину )) # VBO данные (интерливированные вершины) f.write(self.vbo_data.tobytes()) # EBO данные (индексы) f.write(self.ebo_data.tobytes()) else: # 'raw' # Только данные без заголовка f.write(self.vbo_data.tobytes()) f.write(self.ebo_data.tobytes()) print(f"Saved to {output_path}") print(f" Vertices: {self.vertex_count}") print(f" Indices: {self.index_count}") print(f" VBO size: {self.vbo_data.nbytes / 1024 / 1024:.2f} MB") print(f" EBO size: {self.ebo_data.nbytes / 1024 / 1024:.2f} MB") def save_obj_optimized(self, output_path: str): """ Сохраняет оптимизированный OBJ с уникальными вершинами """ if self.vbo_data is None or self.ebo_data is None: self.build_vbo_ebo() with open(output_path, 'w') as f: f.write("# Optimized OBJ\n") f.write(f"# Vertices: {self.vertex_count}\n") f.write(f"# Triangles: {self.index_count // 3}\n\n") # Записываем вершины for i in range(self.vertex_count): v = self.vbo_data[i] f.write(f"v {v[0]:.6f} {v[1]:.6f} {v[2]:.6f}\n") # Записываем нормали for i in range(self.vertex_count): v = self.vbo_data[i] f.write(f"vn {v[3]:.6f} {v[4]:.6f} {v[5]:.6f}\n") # Записываем UV for i in range(self.vertex_count): v = self.vbo_data[i] f.write(f"vt {v[6]:.6f} {v[7]:.6f}\n") # Записываем грани f.write("\n") for i in range(0, self.index_count, 3): i0, i1, i2 = self.ebo_data[i:i+3] + 1 # +1 для индексации с 1 f.write(f"f {i0}/{i0}/{i0} {i1}/{i1}/{i1} {i2}/{i2}/{i2}\n") print(f"Saved optimized OBJ to {output_path}") def get_stats(self) -> Dict: """ Возвращает статистику по модели """ stats = { 'vertices_raw': len(self.vertices), 'faces': len(self.faces), 'normals_raw': len(self.normals) if self.normals is not None else 0, 'texcoords_raw': len(self.texcoords) if self.texcoords is not None else 0, } if self.vbo_data is not None: stats.update({ 'vertices_unique': self.vertex_count, 'indices': self.index_count, 'triangles': self.index_count // 3, 'vbo_bytes': self.vbo_data.nbytes, 'ebo_bytes': self.ebo_data.nbytes, 'total_bytes': self.vbo_data.nbytes + self.ebo_data.nbytes, }) return stats def load_binary(self, filepath: str) -> Tuple[np.ndarray, np.ndarray]: """ Загружает бинарный VBO/EBO """ with open(filepath, 'rb') as f: # Читаем заголовок header = f.read(12) if len(header) == 12: vertex_count, index_count, stride = struct.unpack('III', header) else: # Старый формат без заголовка - пробуем определить размер f.seek(0) data = f.read() # Пробуем интерпретировать как VBO данных # Это сложно, проще использовать формат с заголовком raise ValueError("Invalid or old format, use 'vbo' format") # Читаем VBO vbo_size = vertex_count * stride * 4 vbo_data = np.frombuffer(f.read(vbo_size), dtype=np.float32) vbo_data = vbo_data.reshape(vertex_count, stride) # Читаем EBO ebo_data = np.frombuffer(f.read(index_count * 4), dtype=np.uint32) self.vbo_data = vbo_data self.ebo_data = ebo_data self.vertex_count = vertex_count self.index_count = index_count return vbo_data, ebo_data # Командная строка def main(): parser = argparse.ArgumentParser( description="OBJ to VBO/EBO parser with vectorized operations" ) parser.add_argument("input", help="Input OBJ file") parser.add_argument("-o", "--output", help="Output binary file", default="model.vbo") parser.add_argument("--no-dedup", action="store_true", help="Disable vertex deduplication") parser.add_argument("--format", choices=["vbo", "raw"], default="vbo", help="Output format") parser.add_argument("--export-obj", help="Export optimized OBJ") parser.add_argument("--stats", action="store_true", help="Show statistics only") args = parser.parse_args() # Парсим print(f"Parsing {args.input}...") parser = OBJParser() parser.parse(args.input) if args.stats: stats = parser.get_stats() print("\n Statistics:") for key, value in stats.items(): if 'bytes' in key or 'size' in key: print(f" {key}: {value / 1024 / 1024:.2f} MB") else: print(f" {key}: {value}") return # Строим VBO/EBO parser.build_vbo_ebo(deduplicate=not args.no_dedup) # Сохраняем parser.save_binary(args.output, format=args.format) # Экспорт в OBJ если нужно if args.export_obj: parser.save_obj_optimized(args.export_obj) # Показываем статистику stats = parser.get_stats() print("\n Final statistics:") for key, value in stats.items(): if 'bytes' in key or 'size' in key: print(f" {key}: {value / 1024 / 1024:.2f} MB") else: print(f" {key}: {value}") if __name__ == "__main__": main()
Однозначно - да. На ЯВУ - проще. Но cmd = parts(i) - это действия, которые повторяются каждый байт (при каждом проходе): 1) вычисление адреса parts - это два лишних обращения к памяти: lea rax,parts add parts,i 2) весь перебор if cmd == 'v' и др. - это побайтовый опрос. Это долго. Для "Москвича" это не критично. Но мои объекты - это здания из огромного количества полигонов. Здесь нужны векторные возможности процессора, например, VPCMPISTRI Эта инструкция поможет определить, есть ли в строке нужные токены, а дальше можно уже по байтам разбирать, как с ними работать. Над чем я сейчас работаю: Сколько памяти выделить для буфера вершин? Можно взять с запасом, но это только для "Москвича" годится. Значит, надо считать. Как считать? Ищем строки, которые начинаются с "v". Но это могут быть и "v" с пробелом, и "v" с Tab, и "vn", и "vt". Как мы ищем? С помощью CMPSB? Нет, конечно. CMPSD? Но она сравнивает последовательность байт с последовательностью байт Берём VPCMPISTRI. Она сравнивает независимые 16 байт образца с последовательными 16 байтами памяти и ставит флаги, если находит первый совпавший байт. Очень удобно искать конец строки, если токен уже отработан или не нужен. Это база, а дальше - на любителя. Можно первым проходом заменить весь Tab на пробелы, можно удалить ведущие пробелы в начале строк, можно этого не делать, а просто посчитать количество токенов каждого типа и под них выделить нужное количество буферной памяти, можно идти в лоб и писать сразу в буфер VBO, но тогда получится монолитный объект, и я не смогу, например, открывать капот и двери "Москвича" Так что ЯВУ - это хорошо, но это не наш метод. Если бы я хотел просто и быстро крутить модель, я открыл бы её в SketchUp А мне интересен сам механизм.
Думал я думал - что это? Даже VPCMPISTRI посмотрел в сети - ничего не понял. Код (ASM): Mov eDi, Начальный Адрес Mov eCx, Сколько ClD / StD ; - направление Mov Al, 'v' ; что RepNE ScasB MovZx eBx, Byte ptr [eDi] BT [BitTab], eBx ; по битмап допустимых после 'v' байт ??
SCASB обращается к памяти каждый раз. Точнее, сейчас уже Intel это как-то кэширует (именно "как-то", т.к. это делает их тайный микрокод). Рассмотрим конкретный пример. Пример возьмём попроще - из широко известного в узких кругах пакета tinyobj_loader - см. вложение. Мне нужно узнать, сколько выделять памяти под буфер, т.е. сколько в исходном .obj-файле вершин, нормалей, полигонов, объектов, групп и прочих элементов. Моя задача - не найти символ "v" в строке, а подсчитать количество токенов "v", "vn", "vt", "o", "g" и т.д. в двух файлах: "геометрия.obj" и "материалы.mat". Причём даже встроенный в 3ds Max конвертер сохраняет данные, мягко говоря, не совсем удобно. Он может добавить Tab в начало строки, а может не добавить, он может задать вершины, а только потом объявить токен "o" ("объект"), так, что непонятно, к какому объекту эти вершины потом отнести. Мой алгоритм: Я беру первый значащий символ в строке, сравниваю его с пробелом, табом, LF, CR, EoF, # (комментарий в формате obj) - для каждого случая свой обработчик. Всё. Я определил, что символ значащий, дальше его обрабатываю. Если "o", то увеличиваю счётчик объектов, если "v", то увеличиваю счётчик вершин и т.д. А теперь представьте, что я для этого использую SCASB. 1) Мне надо для каждого токена запускать свой цикл сканирования (а если файл 20 мегабайт? Это не редкость для современной графики) 2) а если v - это не токен, а часть имени объекта, например, o vase - объект "ваза"? Сейчас я просто ищу начало строки, беру в ней первый значащий символ и сравниваю байт (или слово) по адресу rcx с ASCII-кодом первого символа токена: Спойлер: Побитовое сканирование Код (ASM): countObjEntities proc LOCAL pCurrentPosition:QWORD,hHeap:QWORD LOCAL nObjectIndex:DWORD,nMaterialIndex:DWORD PROLOG 100h ;Set the Local Pointer mov rcx,gpObjDataStart mov pCurrentPosition,rcx ;Initialize Counters mov gnTotalObjects,0 mov gnTotalGroups,0 mov gnTotalSubGroups,0 mov gnTotalVertices,0 mov gnTotalNormals,0 mov gnTotalFaces,0 mov gnTotalMtlLibs,0 mov gnTotalIndices,0 ;Initialize Indices mov nObjectIndex,0 lbl_NextLine: mov rcx,pCurrentPosition cmp rcx,gpObjDataEnd jge lbl_EndOfFile ;Check for consistency mov al,byte ptr[rcx] cmp al,0 ;EOF je lbl_EndOfFile cmp al,9 ;Tab je lbl_SkipByte cmp al,0Ah ;CR je lbl_SkipByte cmp al,0Dh ;LF je lbl_SkipByte cmp al,20h ;Space je lbl_SkipByte cmp al,23h ;# OBJ Format Comment je lbl_SkipByte ;Token detection. Arranged by Probability ;mov rcx,pCurrentPosition cmp byte ptr[rcx],76h ;"v" je lbl_TokenStartsWithV cmp byte ptr[rcx],66h ;"f" je lbl_TokenFace cmp dword ptr[rcx],6D657375h ;"usem" in reverse order - part of "usemtl" je lbl_TokenUseMtl cmp byte ptr[rcx],6Fh ;"o" je lbl_TokenObject cmp byte ptr[rcx],67h ;"g" je lbl_TokenGroup cmp dword ptr[rcx],6C6C746Dh; "mtll" in reverse order - part of "mtllib" je lbl_TokenMtlLib jmp lbl_SkipByte ;Count object name: o name lbl_TokenObject: add pCurrentPosition,2 ;skip "o " inc gnTotalObjects jmp lbl_SkipByte ;Count object name: g name lbl_TokenGroup: add pCurrentPosition,2 ;skip "g " inc gnTotalGroups jmp lbl_SkipByte ;Count material: "usemtl" lbl_TokenUseMtl: add pCurrentPosition,7 ;skip "usemtl " inc gnTotalSubGroups jmp lbl_SkipByte ;Branch V lbl_TokenStartsWithV: inc pCurrentPosition cmp byte ptr[rcx],6Eh ;"n" je lbl_TokenNormal cmp byte ptr[rcx],74h ;"t" je lbl_TokenTextureCoordinate ;jmp lbl_TokenVertex ;By default ;Count vertex: v x y z lbl_TokenVertex: inc pCurrentPosition ;skip "v " inc gnTotalVertices jmp lbl_SkipByte ;Count normal: vn x y z lbl_TokenNormal: add pCurrentPosition,2 ;skip "vn " inc gnTotalNormals jmp lbl_SkipByte ;Count texture: vt u v w lbl_TokenTextureCoordinate: add pCurrentPosition,2 ;skip "vt " inc gnTotalTextureCoords jmp lbl_SkipByte ;Count face: f v/vt/vn v/vt/vn v/vt/vn lbl_TokenFace: inc pCurrentPosition ;skip "f " inc gnTotalFaces jmp lbl_SkipByte ;Count material: "mtllib" lbl_TokenMtlLib: add pCurrentPosition,7 ;skip "mtllib " inc gnTotalMtlLibs jmp lbl_SkipByte lbl_SkipByte: mov rcx,pCurrentPosition cmp rcx,gpObjDataEnd jge lbl_EndOfFile mov al,byte ptr[rcx] cmp al,0 ;EOF je lbl_EndOfFile cmp al,0Ah ;LF je lbl_SkipLF cmp al,0Dh ;CR je lbl_SkipCR inc pCurrentPosition jmp lbl_SkipByte lbl_SkipCR: inc pCurrentPosition mov rcx,pCurrentPosition cmp byte ptr[rcx],0Ah ;LF je lbl_SkipLF jmp lbl_NextLine lbl_SkipLF: inc pCurrentPosition jmp lbl_NextLine lbl_EndOfFile: cmp gnTotalObjects,0 jne @f LOG_TEXT szErrNoObjects @@: cmp gnTotalGroups,0 jne @f LOG_TEXT szErrNoGroups @@: cmp gnTotalSubGroups,0 jne @f LOG_TEXT szErrNoSubGroups @@: cmp gnTotalMtlLibs,0 jne @f LOG_TEXT szErrNoMtlLibs @@: cmp gnTotalVertices,0 jne @f LOG_TEXT szErrNoVertices @@: cmp gnTotalNormals,0 jne @f LOG_TEXT szErrNoNormals @@: cmp gnTotalTextureCoords,0 jne @f LOG_TEXT szErrNoTextureCoords @@: cmp gnTotalFaces,0 jne @f LOG_TEXT szErrNoFaces @@: ;Success mov rax,1 jmp lbl_End lbl_WinError: call SpellWinError xor rax,rax ;jmp lbl_End lbl_End: EPILOG countObjEntities endp Но это неудобно тем, что я для чтения каждого байта обращаюсь к памяти, гоняю этот несчастный байт по всем шинам и контроллерам. SSE предлагает хорошее решение - VPCMPISTRI: Я создаю в памяти шаблон поиска - 4, 8 или 16 байт для интересующих меня символов, т.е. для поиска начала строки и пропуска пустышек это 10h, 13h, 20h, 9, для обработки токенов это ASCII-коды o, g, v, m (первые буквы токенов). А дальше - дело техники: xmm0 = шаблон поиска, xmm1 или адрес в памяти - сканируемый участок, imm = маска (ANSI или Unicode и другие настройки). Как только VPCMPISTRI встречает в заданном диапазоне любой символ из шаблона, она возвращает в RCX смещение этого символа и устанавливает CF. Кстати, если она встречает в заданном диапазоне 0, то устанавливает ZF, что очень важно, т.к. можно проскочить EoF. Вот над этим я и работаю: массово сканирую в поисках 10h, 13h, 20h, 9, а дальше точечно обрабатываю значимые данные. Надеюсь, понятно разложил.