-
- ? Дмитрий
- 06.01.2018 12:13
А что не так? Бинарник подразумевает формат "адрес, длина, данные". 00 02 - адрес загрузки, 08 00 - длина, далее код.
- ? Дмитрий
- 05.01.2018 17:30
Наткнулся на использование системных регистров 177700-177704. Есть инициализация начальных значений, но нет использования. Для чего эти регистры?
- ? Dimaxx
- 02.01.2018 19:53
>> а нанятый мною разработчик
А где он? Прошло 1,5 года, как он "взял отпуск" и с тех пор ни слуху ни духу.
- ? Dimaxx
- 02.01.2018 19:53
>> а нанятый мною разработчик
А где он? Прошло 1,5 года, как он "взял отпуск" и с тех пор ни слуху ни духу.
- ? Дмитрий
- 28.12.2017 22:59
>> для ДИСКОВОДА.
Тогда я что-то не врубаюсь. У меня в эмуле от gid стоит загрузочным 3.17, прекрасно работает с дисководами (на реальной не пробовал). Что не так?
- ? Дмитрий
- 28.12.2017 17:44
>> на БК только кнопка reset имеет наивысший приоритет
Я бы назвал это исключительным приоритетом. :)
- ? Дмитрий
- 28.12.2017 17:42
Есть же МКДОС 3.17, зачем 3.15?
- ? Дмитрий
- 28.12.2017 13:16
>> второй не знаю
Первый - пауза проца, второй переключает частоту. Т.е. переключать частоту на ходу.
- ? Дмитрий
- 24.12.2017 21:30
>> Как обходили.
>> повторишь?
Хоть стреляй - не помню. Давно было. И довольно долго мучился, пока заработало. А вот как - не помню.
- ? Дмитрий
- 22.12.2017 16:35
>> весьма противоречивые ответы от Дмирия.
Я на память СМК переделывал стандартный RAM-диск из комплекта МК-ДОС. Работало. Было удобнее, поскольку RAM-диск портился при запуске игр для 11(М), использующих доп. память. Память читал/писал линейно - точно также как и подключенную доп. 11М (то бишь подключив в окна 40000(8) и 100000(8) страницы памяти 11М мог обнулить всю память с 1000(8) до 137777(8) за один цикл) без каких-либо шаманств... Хотя нет, шаманства с СМК были в плане странно (я бы сказал идиотски) подключаемых сегментов памяти.
- ? Дмитрий
- 22.12.2017 14:35
>> не могу же я сразу записать со 100000 все 32к.
Как раз запросто, емнип, кроме 177000+(8). Данные пишутся в сегмент, подключенный к определенному окну в адресном пространстве. Что не так?
¤
>> 2. Копирую блоками по 4к или как? или как выше спрашивал?
>> 3. как читать? линейно или блоками?
Линейно, как и в любой другой области памяти.
- ? Дмитрий
- 17.12.2017 11:39
>> когда мы вставляем блок МСТД в системный разъем, то фокал загружается по умолчанию. Так? Значит есть возможность загрузить и нашу приблуду безо всяких команд.
Говорилось уже - микросхема Фокала "садится" на МПИ вместо Бейсика, поэтому работает без загрузчиков. Пик (или его ПЗУ) на шину просто так не посадишь. В любом случае нужна ПЗУ, замещающая Бейсик как Фокал, но с загрузчиком для пика.
- ? Дмитрий
- 10.12.2017 22:09
>> Ассемблер запускать, небольшие файлы сохранять...
Для этого и эмулятора за глаза, незачем железку насиловать.
- ? Дмитрий
- 28.11.2017 13:57
>> берем бит 4, ставим в 1 (включаем движок), читаем бит 4 - если равен 1, то вращается, 0 - не включен двигатель.
Тьфу, ставим бит 4, ждем бит 7, проверяем бит 4.
- ? Дмитрий
- 28.11.2017 13:55
>> я сделал предположение для чего
Это предположение - точного объяснения, видимо, уже нет надежды узнать.
¤
>> потому что битов не хватит.
Почему? Нам нужно, по идее, избавиться от задержек. Полностью свободных битов в 177130 по чтению и записи - 4 (бит 3 и 11-13). Идем по списку в gid - 27.11.2017 13:02:
¤
>> 1. позиционирование с дорожки на дорожку.
Дали команду ШАГ и ждем бит 7 в цикле. Шаг выполнен, идем дальше. Задержка не нужна.
¤
>> 2. INDEX: П/п проверки диска и числа оборотов.
Дисководов у нас всего 2 (да и 2 много, достаточно 1, есть же винт). Значит берем бит 4, ставим в 1 (включаем движок), читаем бит 4 - если равен 1, то вращается, 0 - не включен двигатель. Задержка не нужна.
¤
>> 3. FINDH: П/п поиска адресного маркера
Вот тут покумекать, ибо команды, как таковой, нет.
¤
>> 4. STREAD: П/п запуска чтения и т.п. управление дисководом
В самой STREAD короткий цикл также заменить на TSTB (R4)+BPL. Установили бит ЧТЕНИЕ, ждем ответа контроллера, снимаем бит, идем дальше. Задержка не нужна. С записью точно также.
¤
С индексом надо подумать как избавится от задержки. В итоге пока(!) ни один доп. бит не использован.
¤
>> Но с ВП1-128 он уже не будет иметь ничего общего
К сожалению с ВП-128 вообще ничего больше не будет иметь общего. Все это умерло. И флопы умерли и не выпускаются более. С большим объемом памяти и быстрым процом проще программно сэмулировать флоп из образа на винте прямо в эмуляторе/симуляторе.
¤
Все-таки есть где-нить задержки флопов в миллисекундах? Если нет, то как рассчитать? По циклам SOB будет примерно (хм, а надо ли точнее?). Я сам на своем TEAC в давние времена уменьшал задержки до минимума, пока не начинали сыпаться ошибки чтения/записи/форматирования, чуть увеличивал для надежности и использовал.
- ? Дмитрий
- 28.11.2017 11:36
>> Изменить чуток логику работы эмулятора FDD
Это на будущее - все равно уже новых БК не будет в железе, остаются эмуляторы.
- ? Дмитрий
- 28.11.2017 11:33
>> ПЗУ, в котором драйвер, что на 10ке, что на 11ке одно и то же, и работает с одной и той же скоростью
Вот про этот момент я забыл за давностью лет. Помню, говорилось где-то, что код в ПЗУ работает быстрее, чем в ОЗУ. А вот про то, что ОЗУ 11-й тормознее - не знал.
¤
>> Число тактов команды 11-й больше, чем у 10-ки потому, что ОЗУ тормознутое.
А вот тут сразу возникает вопрос: если драйвер в ПЗУ, проц один и тот же (внутри проца, на железном уровне, скорость выполнения в тактах одна и та же), то почему скорость выполнения одинаковая, раз команда, допустим SOB, будет выполняться чуть быстрее на частоте 4Мгц супротив 3Мгц??? 4 млн тактов в секунду против 3-х. У нас ведь условия одинаковые, кроме частоты! И разгон подразумевает увеличение скорости выполнения команд и, следовательно, увеличение производительности. Пусть в случае ВМ1 она мизерная.
¤
>> делается чтение 15 слов данных без готовности данных
Меня тоже смутил данный код - тупо читаем регистр без сохранения. Для чего? И почему именно 15, а не 16, 12, 22?
¤
>> Я вижу там несколько моментов:
¤
Интересно было бы узнать оптимальные задержки флопа в миллисекундах - индекса, перехода с дорожки на дорожку, адр. маркера и прочего. Ведь на большой частоте проца проще будет реализовать sleep и юзать его (он будет независим от частоты).
¤
>> Всё это станет критично, если с повышением частоты проца будет соответственно увеличено и быстродействие памяти, в которой находится драйвер.
Вот тут у меня тоже затык. Тупо увеличить задержки кратно с повышением частоты мб и прокатит, но и прочие команды тоже будут работать быстрее. С винтом все ясно - если мне склероз не изменяет, то там проверка готовности по биту, а не задержка. Отсюда вопрос: а почему у FDD не сделать проверку по битам? Изменить чуток логику работы эмулятора FDD и сделать выставление бита готовности после операций. Дали команду перехода на дорожку - ждем бита готовности. Проверка диска тоже битом (у нас же эмуль работает не с реальным железом, а с его эмулятором). Это было бы критично, если бы в системе стоял реальный флоп и эмуль пробрасывал бы команды к нему. А раз у нас эмулятор всего: дали команду включения движка - выставили бит (можно из числа неиспользуемых), отключили движок - бит сбросили. Я хотел именно так сделать. Но как быть с адресным маркером? Ведь команды его поиска нет. Либо также бит сделать - в эмуле эмулируется оборот диска. Вот и выставить бит когда маркер "под головкой" и сбрасывать, когда маркер "ушел". Тогда драйвер будет ловить бит и идти дальше безо всяких задержек.
- ? Дмитрий
- 25.11.2017 14:33
Кстати, если задержки в драйвере флопа важны, то почему тот же СМК отлично работает и на 10-ке, и на 11? Задержки не меняются, а частота 11-й увеличена на треть относительно 10-ки. С точки зрения задержек это очень много, однако все работает без проблем. Но если глянуть в расчетные тайминги, то получается полный пушной зверек. Число тактов одной и той же команды у 10-ки и 11-й различается! Причем число тактов команды 11-й больше, чем у 10-ки. Как такое может быть? Проц-то один и тот же, только у 11-й частота выше на 1Мгц. Т.е. задержки должный быть равны в таблицах 10 и 11.
- ? Дмитрий
- 17.11.2017 13:07
С таймингами БК11 2 млн тактов исполняются за 18 мс. И получается все остальное исполняется за 22+ мс. Действительно,
¤
>> эмуляция инструкций - это мелочь
- ? Дмитрий
- 17.11.2017 12:02
БКшный проц реально медленный. Провел эксперимент на "своих" таймингах:
¤
базовый 12 тактов, адресации (пока от балды):
Rn = 0 тактов
(Rn) = 1 такт
(Rn)+ = 1 такт
@(Rn)+ = 2 такта
-(Rn) = 1 такт
@-(Rn) = 2 такта
E(Rn) = 3 такта
@E(Rn) = 4 такта
SOB = 20 тактов (как на ВМ1), остальное не использовалось.
¤
Реализация (пока взят класс CPU, добавлена поддержка 32-битных инструкций) на паскале кода эмулятора gid'а. В итоге 2 млн. инструкций (взят 32 битный алгоритм вычисления таблицы по полиному для подсчета CRC32 и зациклен с изменением полинома на каждом шаге) исполняются на частоте проца "хоста" (рабочий комп старый, i3-3220 3,3ГГц) от 14 до 27 мс. Дома проверю на скае i5-6500.
¤
Попробую взять тайминги БК из эмуля и еще раз запущу.
- ? Дмитрий
- 17.11.2017 09:58
Ну и насчет QueryPerformanceCounter - http://www.virtualdub.org/blog/pivot/entry.php?id=106
- ? Дмитрий
- 17.11.2017 09:42
>> что-то нереальное делается на каждую команду процессора
Выборка кода команды, декодирование методов адресации, выборка из памяти (если требуется), исполнение, установка признаков, запись в память (если требуется). Ничего лишнего, что необходимо, то и выполняется. Если ускорять, то только переписывать на чистый асм. И то не факт, что сильно ускорится.
¤
>> смотрим, сколько времени осталось до вывода этого кадра, Sleep()
Такой способ имеет большую погрешность и сильно плавает. К тому же в момент паузы нужно отдавать кванты времени ОС, а не тупо грузить поток. Вот интересная статья на тему задержек https://habrahabr.ru/post/75234/
- ? Дмитрий
- 16.11.2017 22:47
>> а переношу в следующий фрейм
Абсолютно верно. Про этот момент я забыл.
¤
>> На большее просто не хватает вычислительных возможностей
Тогда мб сделать так - все, что надо вывести и что требует достаточно времени, засунуть в свой отдельный поток, который запускать после выполнения фрейма (основной поток идет дальше, не отвлекаясь), а после завершения всех операций останавливается автоматически. Туда сунуть вывод видеоОЗУ, регистров, признаков и прочего. Или подобное уже опробовано и не принесло результатов?
¤
>> максимальная эмулируемая частота ВМ1 может быть 20МГц
Это с реальными таймингами команд ВМ1 или это независимо от таймингов? Если тайминги не влияют, то надо всю эмуляцию команд переписывать на асм.
¤
В общем неутешительно это. Хотя эмуляторы сонек, ARMов существуют и быстродействие там повыше будет, чем БК. Значит возможно как-то уложить быстродействие.
- ? Дмитрий
- 16.11.2017 18:58
Переврал все что можно. Оказалось решение легче и проще. Пример тот же - частота 50МГц, фрейм 40мс, 25 фреймов/сек, 2 млн. тактов на фрейм. Итого: время выполнения 2 млн тактов на симулируемом проце 2.000.000/50.000.000 секунд. Переводим это число в 100нс интервал. А теперь запускаем таймер с этим интервалом ДО выполнения фрейма и прочего. Что получаем: пока таймер тикает - выполняем ExecuteFrame, выполняем вывод на экран видеоОЗУ, вывод признаков, регистров, звуковых буферов и т.д. и т.п. В конце этого просто через WaitForSingleObject ждем оставшееся время до завершения таймера. Получаем приблизительно точную частоту исполнения команд.
- ? Дмитрий
- 16.11.2017 16:45
>> Кстати, что мешает поэкспериментировать? задать интересующее значение и пересобрать эмулятор, многие вопросы могут отпасть сразу.
Мешает полное незнание VS и экспериментирование не совсем то, что надо. Частота может меняться, а под каждое изменение подгонять константу - не комильфо.
¤
К тому же во фрейм не обязательно каждый раз одинаковое число тактов уложится. Значит надо учитывать это "переполнение". К примеру частота 50Мгц. 50 млн тактов в секунду. Фрейм 40мс, 25 фреймов/сек. Получается во фрейм надо "уложить" 2 млн тактов в идеале. Но так может быть не всегда (а то и вообще редко). А будет, к примеру, 2000012 тактов. Хотя этими 12 тактами можно пренебречь. И вот как теперь вычислить задержку для SetWaitableTimer, если на одном компе этот фрейм выполнится за одно время, на другом - за другое, а на третьем - за третье? Упрощенно:
¤
LARGE_INTEGER Freq,Start,End;
¤
QueryPerformanceFrequency(&Freq);
QueryPerformanceCounter(&Start);
ExecuteFrame();
QueryPerformanceCounter(&End);
¤
// время выполнения текущего ExecuteFrame в секундах
double time = (End - Start) / Freq;
// в 100 наносекундных интервалах (в "формате" SetWaitableTimer) это будет
time *= 1E7;
¤
это универсально, для любой машины. И как теперь узнать - сколько таких интервалов будет для моих 50МГц? Тут мысль стремительно ускользает. А в итоге: полученное значение минус time и будет задержкой для SetWaitableTimer.
- ? Дмитрий
- 16.11.2017 14:21
Чет я уже мозг сломал - никак не допру как вычислить задержку между фреймами в зависимости от частоты проца, фреймрейта и пр. В исходниках, видимо, приблизительная константа, ибо
¤
//liDueTime.QuadPart = -10000000LL / CPU_FRAMERATE;
liDueTime.QuadPart = -9000000LL / CPU_FRAMERATE; //вот такая пока фигня будет - полное игнорирование реальности.
¤
Чем не устроило число -10 млн (не понял - оно завязано на 3 или 4Мгц или взято с потолка, лишь бы примерно соответствовать скорости) и было заменено на -9 млн?
- ? Дмитрий
- 15.11.2017 21:40
>> в последней доступной версии я исправил функцию UINT CMotherBoard::TimerThreadFunc()
А что, кстати, было переписано в ней в 3.9? Я разбирался с 3.8, сегодня качнул сырцы 3.9, глянул эту функцию и не увидел изменений.
¤
>> Трудно будет, если надо будет загружать/сохранять данные из страниц БК11, которые в данный момент не подключены.
А что сложного? Память в эмуле линейная, единым массивом - вычислить смещение по номеру страницы, подключена или нет - значения не имеет. Главное загрузить в нужную страницу, а потом после старта программно переключать страницы и юзать загруженное. Либо в окне просмотра страниц памяти СМК/БК11 сделать кнопку загрузить/сохранить, указать адрес смещения от начала страницы и длину, и читать/писать в файл.
¤
>> зависит от режима работы, ровно так же, как и воплощённый в железе
Так вот и интересовал этот момент - как в железе реализовано? Перезапускается ли таймер при смене делителя или продолжает работать с новым делителем. :)
¤
>> У меня тогда не было кросс ассемблера
асм я себе тоже уже сделал (заодно и стандартного ВМ1), с покером и профурсетками - без него никуда, надо же как-то проверять работу команд FPU и префиксов. Не писать же вручную. Все реализации работают нормально. Щас надо выяснить макс. возможную частоту работы симуляции команд, чуток полирнуть и можно приступить к таймерам. Асмы БК забыл напрочь - полчаса вспоминал клавиши управления и команды входа в редактор/выхода/компиляции и чтения/записи.
- ? Дмитрий
- 15.11.2017 16:21
>> не умеют делать задержку даже на 1мс
В курсе. Поэтому использую sleep в крайнем случае, когда нужна просто задержка, не критичная по времени исполнения. Но sleep использовать необязательно. Можно запустить обработку диспетчера сообщений как в CMotherBoard::StopTimerThread, чтоб обрабатывал очередь, а после обработки одного события отслеживать величину задержки - так и GUI не будет подвисать во время цикла.
¤
По вектору 100 я в курсе. Про встроенный конечно. Имелось в виду, что я настроил и запустил счет, допустим, с делителем скорости 4, а через некоторое время изменил делитель на 16, не меняя другие параметры и не останавливая счет. Встроенный таймер при этом продолжит считать с новым делителем или сначала перезапустится, перезагрузив счетчик?
¤
Кстати, в эмуле неплохо было бы сделать возможность в режиме паузы проца напрямую загрузить/сохранить участок памяти из/в файл(а), чтоб не пихать его в образ, потом грузить и т.д. Экпериментировал тут как-то с алгоритмом и задолбался копировать файл в образ, чтоб потом его загрузить в отладчике. Напрямую писать и читать память эмулируемой машины было бы гораздо удобнее.
- ? Дмитрий
- 15.11.2017 11:46
Как все-таки лучше организовать задержку в потоке? Юзать WaitableTimer или sleep в цикле с приблизительным отсчетом через связку QueryPerformance*? А то в комменте написано, что QueryPerformance отлично работает на 10-ке, а на ХР плохо (почему?) и наоборот. И как ведет себя таймер на БК, если менять параметры - начинает работать с новыми параметрами или перезапускается при любом изменении (к примеру множитель)?
- ? Дмитрий
- 14.11.2017 20:25
Мда, там черт ногу сломит. Особливо с моими скромными познаниями в Си.
- ? Дмитрий
- 14.11.2017 17:04
А где эти исходники эмуля ДВК?
- ? Дмитрий
- 14.11.2017 13:09
>> В хедере есть функция IsFrameExecuted()
Млин, вот терпеть ненавижу этот Си из-за таких допущений. Одна ф-ция объявлена в одном месте, а реализована в другом, другая и объявлена, и реализована в одном месте. И ищи ее по всем файлам. Нет, чтобы строго .h - объява, .c - реализация.
¤
И что-не наблюдаю таймер по 100 вектору, 177706-177712 вижу. Плюс, если реализовывать 177706-177712 в отдельном потоке, то как взаимодействовать с ним в случае, если от ЦПУ поступил запрос чтения счетчика? Ведь поток таймера работает (ибо работает ExecuteFrame) - и как правильно считать значение (не с точки зрения Сишных потоков, а вообще, без привязки к ЯВУ)? В сырцах 3.8 в комментах сказано, что таймер надо в отдельный поток выносить, а не выполнять каждый 128 такт.
- ? Дмитрий
- 14.11.2017 10:26
Вопрос: вот в CMotherBoard::ExecuteFrame на время выполнения процедуры вызывается лочилка m_lockExecuteFrame.lock. Что дает этот лок? Что он запрещает? Вызов m_lockExecuteFrame.lock/unlock есть только внутри ExecuteFrame (равно как и обращение к классу m_lockExecuteFrame). А зачем он, если извне его никто не контролирует? Это ведь отдельный экземпляр класса LockVarType. Был бы глобальным, тогда понятно - пока он залочен, другие потоки ждут его разлочки, чтобы начать выполнять свои действия или обрабатывать результаты выполнения другого потока. Точно также непонятно назначение m_lockRunning в классе мамки - лочится при вызове CMotherBoard::RunCPU и анлочится при CMotherBoard::StopCPU. И, опять-таки, его никто не контролирует - проверок lockRunning.IsLocked нет. Загадка...
- ? Дмитрий
- 13.11.2017 11:36
Просто в некоторых местах потомков юзается увеличение тика. Раз юзается, значит нужно. В некоторых нет никаких вызовов родителя. Мне неясно для чего нужны эти тики Device?
- ? Дмитрий
- 13.11.2017 09:49
Как используется и для чего? И нужен ли он вообще?
- ? Дмитрий
- 12.11.2017 21:56
Дошли руки до продолжения погружения с исходники эмуля. Интересует назначение и принцип действия класса Device - код вроде простой, но для чего он и как используется пока неясно.
- ? Дмитрий
- 13.10.2017 13:40
А SoundDrive не на двух ли AY был? Или на 2х AY был на спеке? Чет я уже запамятовал.
- ? Дмитрий
- 25.09.2017 14:27
>> контроллер формирует отдельную последовательности котов при нажатии 1 шт. кнопки, и др. последовательность - при её отпускании
Нашел, благодарю. Там все просто. Если нажата клавиша, то выдается ее скан-код. Если клавиша отжата, то передается 0xF0 и ее код. Контролируя поступающие скан-коды можно отлавливать хоть сколько нажатых одновременно клавиш.
- ? Дмитрий
- 25.09.2017 00:05
Не буду создавать отдельную тему, спрошу тут. Интересует инфа про принципы работы контроллера клавы в РС - отлов нажатия нескольких клавиш и прочего. Плюс интересует инфа по программируемому таймеру РС. У Фроловых в "Библиотеке программиста" инфа есть, но не въеду как определяется нажатие 2-х и более клавиш. Где можно еще почитать?
- ? Дмитрий
- 27.08.2017 15:41
Что-то я уже стал подзабывать тонкости генерации кода. Команда (если ее разместить по адресу 1020) MOV 1000,1002 генерирует код 16767 177754 177754. Если я правильно помню, то вычисление смещения происходит от адреса следующей команды (1026). То бишь, смещения должны быть равны 1000-1026=177752 и 1002-1026=177754. Или при разборе двухоперандной команды с адресациями 67 в обоих операндах процессор опирается на адрес после считывания очередных данных?
- ? Дмитрий
- 24.08.2017 22:35
Вопрос к знатокам. Были, емнип, на БК часы реального времени - есть у кого инфа про адреса портов, явки, пароли, биты, формат хранения и прочее? И сколько вариантов было?
- ? Дмитрий
- 10.08.2017 17:16
>> Если доведем до ума (ну вдруг) и владелец прав даст добро - выложу.
Как успехи? Есть какие-нить результаты?
- ? Дмитрий
- 30.07.2017 12:28
Форум мб не помрет, а вот новую БКшку так и не возродили.
- ? Дмитрий
- 15.07.2017 22:10
FON и я делал давным-давно, а вот TTF не видел.
- ? Дмитрий
- 04.07.2017 17:55
>> завершение работы
Чего именно? Винды, программы? Какие сообщения выдает? Фото приложить.
¤
>> может у меня нехватает длл? каких
Если не хватает длл, то программа просто не запустится с сообщением об отсутствии длл с ее именем.
- ? Дмитрий
- 04.07.2017 17:28
Что за вылеты? Нужна более подробная инфа. Сколько не тестировал - ни одного вылета.
- ? Дмитрий
- 03.07.2017 17:07
БК начал "умирать" до выхода Дума и к его выходу никто не стал заморачиваться с созданием чего-либо подобного. Дум на спеке был только в демо в '97, полноценной игры так и не сделали. Подобие винды на БК также было только в демо на закате БК.
- ? Дмитрий
- 03.07.2017 09:56
>> Нормальные на всем известном игровом автомате были. А эти неизвестно что.
С таким успехом можно требовать Дум на БК. Он же есть на РС, значит и на БК должен быть. Игровой автомат и БК несколько разные вещи. Классический морской бой - игра на клетках, а на автомате совсем другой МБ.
- ? Дмитрий
- 02.07.2017 16:06
>> Вроде бы железо возрождается
Эээ, в каком месте? Проект Воланда заглох видимо навсегда. А 11(М) никому не нужна в плане программирования.
¤
>> А вот морского боя так я и не нашел на БК
Был, и неплохой.
- ? Дмитрий
- 02.07.2017 16:03
Слишком большой гемор...
-
«
1 | ... | 5 | 6 | 7 | 8 | 9 | »
?