Оберон-клуб «ВЄДАsoft»

Твердыня модульных языков
Текущее время: 21 ноя 2017, 16:00

Часовой пояс: UTC + 2 часа




Начать новую тему Ответить на тему  [ Сообщений: 5 ] 
Автор Сообщение
СообщениеДобавлено: 17 мар 2013, 20:30 
Не в сети
Аватара пользователя

Сообщения: 843
Откуда: Днепропетровская обл.
Сперва — о том, зачем создавать библиотеки именно для ZXDev, ведь есть чудесный кросс-ассемблер SjASMPlus.

Во-первых, для быстрого макетирования часто используют ZX-Basic, но это имело больше смысла при разработке на самом Спектруме, которая уже никогда не будет актуальной — мощные хост-платформы сверхдоступны. Но зато возрастает потребность в структурировании методов разработки, поэтому макетирование на Spectrum-Basic выглядит наивно. Нужно использовать другой язык, более подходящий для структуризации данных. В этом свете видится единственно возможное решение — язык высокого уровня с высоким качеством кодогенерации. В ZXDev это есть, и даже больше — это Си и Оберон-1 и 2.

Во-вторых, нужен гибкий инструмент для разработки библиотек и эффективного их использования (в идеале с возможностью вызывать процедуры, работая как на языке высокого уровня, так и на ассемблере; чтобы в целевую программу включались только лишь реально использованные фрагменты кода; и, наконец, была бы гибкая конфигурация параметров библиотек). И всё это тоже есть в ZXDev.

Вот какой механизм конфигурирования библиотек предлагается. Допустим, у нас есть графическая библиотека, и в ней процедура вывода точки по заданным координатам. При аналогичном случае использования данной процедуры рассмотрим 3 варианта, которые могут быть предусмотрены её конфигуратором:

    1. Пишется компактный загрузчик. Нужно выводить точки только в верхней части экрана, быстродействие процедуры вывода точки не важно.
    2. Пишется очень большая игра, использующая всю память; необходим вывод точки максимально быстрый, но ради экономии памяти не использующий заранее рассчитанные таблицы данных.
    3. Демка среднего размера, которой нужна максимальная скорость рисования точки. Ввиду наличия свободной памяти можно использовать таблицы данных для ускорения работы.

Предлагаемый механизм конфигурирования библиотек основан на препроцессоре языка Си (но может использоваться и для разработки на Обероне), годится для всех трёх случаев и не требует перекомпиляции библиотеки (которая может быть довольно длительной по времени). Файл конфигурации помещается в папку с исходниками и выглядит так:
Код: "C"
/* Configuration file of the library Graph */
 
//#define PLOT_ROM
//#define PLOT_FAST_NO_TABLE
#define PLOT_FAST_TABLE
 
#define TableStart 0xF000 // If table used

А вот как достигается эффект “без перекомпиляции”. В самом заголовочном файле:
Код: "C"
#ifdef PLOT_ROM
#define Graph_Plot Graph_Plot_ROM
#endif PLOT_ROM
 
#ifdef PLOT_FAST_NO_TABLE
#define Graph_Plot Graph_Plot_NoTab
#endif PLOT_FAST_NO_TABLE
 
#ifdef PLOT_FAST_TABLE
#define Graph_Plot Graph_Plot_Tab
#endif PLOT_FAST_TABLE
Подобным же образом можно задать тип аргументов для вывода точки (байт — если достаточно вывода только в пределах экрана; и слово — если нужен вывод точек за пределами экрана), какие использовать алгоритмы рисования линий, кругов, заливки замкнутого контура, вывода спрайтов, тайлов и т.д. Или каким способом выводить символьную информацию — с помощью RST #10 (ПЗУ) или напрямую в видеопамять. Отдельно можно ввести опции, задающие какие управляющие коды будет понимать процедура вывода, будет ли прокрутка при достижении низа экрана и т.п. Таким же способом можно задать режим работы программы — IM0/IM1, IM2 или DI. Понятное дело, что для возможности переключать такое большое количество опций без перекомпиляции потребуется дублировать много кода в библиотеке, но это неважно. Главное, что будет достигаться единостильность. А в случае кросс-платформенной разработки с помощью такого конфигуратора будут скрываться различия между платформами.

В будущем хотелось бы иметь визуальный редактор свойств модулей и компонентов, подобный дельфийскому. Возможно, такой инструмент появится в среде XDev.

Надеюсь, я вас убедил, что накопление библиотек в стиле, задаваемом XDev, имеет плюсы помимо простоты разработки на Обероне. Ведь массив библиотек может быть использован и для разработки на Си, и на ассемблере, и об этом читайте ниже. Приступаем к делу. Нам понадобится базовое знание ассемблера Z80.

Адаптируем для ZXDev из YS MegaBasic V4.0 процедуру INVERT без параметров. Запустите MegaBasic и наберите INVERT. Она инвертирует весь экран Спектрума — меняет местами засвеченные и погасшие точки. Примем рабочую гипотезу, что где-то в процедуре должна быть загрузка в регистр начального адреса экранной памяти: #4000. В дебаггере эмулятора EmuZWin вызываем инструмент “Поиск”(Find). Будем искать два байта 00 40 (В Z80 первым хранится младший байт). Опытным взглядом сразу находим участок кода по адресу #C053:
Код: "ASM"
C053 ; ---------------------------------------------------------------------------
C053
C053 INVERT:
C053 ld hl, 4000h
C056 ld bc, 1800h
C059
C059 loc_C059: ; CODE XREF: C061
C059 ld a, (hl)
C05A xor 0FFh
C05C ld (hl), a
C05D dec bc
C05E inc hl
C05F ld a, b
C060 or c
C061 jr nz, loc_C059
C063 ret
C064 ; ---------------------------------------------------------------------------
(Я использовал лучший дизассемблер всех времён и народов — IDA, который понимает и процессор Z80!)

Создадим такой Оберон-интерфейс:
Код: "OBERON"
  1. MODULE Mega;
  2. (* -------------------- *)
  3. (* MEGA BASIC for ZXDev *)
  4. (* by Oleg N. Cher *)
  5. (* VEDAsoft, 2013 *)
  6. (* -------------------- *)
  7.  
  8. PROCEDURE INVERT*; END INVERT;
  9.  
  10. END Mega.
Сохраним его в ZXDev/Lib/Mod/Mega.odc и скомпилируем нажатием F11. У нас создались файлы:

    ZXDev/Lib/Obj/Mega.c
    ZXDev/Lib/Obj/Mega.h
    ZXDev/Lib/Sym/Mega.sym

Скопируем файлы ZXDev/Lib/Obj/Mega.h и ZXDev/Lib/Obj/Mega.c в ZXDev/Lib/C
Это делается затем, что папка Obj содержит автоматически сгенерированные Ofront’ом сишные исходники, а у нас ручная работа. Т.е. нам от интерфейсного пустого модуля нужен лишь символьный файл, своеобразный биндинг между Обероном и Си/ассемблером.

Отредактируем исходник ZXDev/Lib/C/Mega.c (для редактирования программ на Обероне, Си и PHP я часто использую редактор Syn Text Editor). Он примет такой вид:
Код: "C"
/* -------------------- */
/* YS MegaBasic v4.0 */
/* (c) 1984 Mike Leaman */
/* MEGA BASIC for ZXDev */
/* by Oleg N. Cher */
/* VEDAsoft, 2013 */
/* -------------------- */
 
#include "SYSTEM.h"
#include "Mega.h"
 
export void Mega_INVERT (void);
 
/*================================== Header ==================================*/
 
/* "INVERT" - инвертирование экрана: тон становится фоном, а фон - тоном. */
void Mega_INVERT (void)
{
__asm
LD HL, #0x4000
LD BC, #0x1800
INV_LOOP$:
LD A, (HL)
XOR #0xFF
LD (HL), A
DEC BC
INC HL
LD A, B
OR C
JR NZ, INV_LOOP$
__endasm;
} //Mega_INVERT
Как видите, я перевёл ассемблерный текст в заглавные буквы (традиция ZX). Пришлось также поправить числа — в ассемблере sdas, использованном в ZXDev, шестнадцатиричные числа задаются как 0xЧИСЛО, а решёточка # ставится перед всеми вычисляемыми выражениями (и числами). Знак $ после имени метки обозначает, что она локальная. Примеры смотрите в поставке ZXDev.

ZXDev/Lib/C/Mega.h приведём в такой вид:
Код: "C"
 
#ifndef Mega__h
#define Mega__h
 
#include "SYSTEM.h"
 
 
import void Mega_INVERT (void);
#define Mega__init()
 
 
#endif
Определение пустого Mega__init() необходимо если не требуется инициализация модуля (если требуется, то это будет процедура инициализации).

Откроем наш интерфейс ZXDev/Lib/Mod/Mega.odc и соберём библиотеку нажатием F12. Если ошибок нет, то консольное окно после трансляции исчезнет. Удобно, не надо заострять на нём лишний раз внимание.

Напишем небольшой модуль для тестирования нашей процедуры INVERT:
Код: "OBERON"
  1. MODULE DemoInvert;
  2. IMPORT Console, Mega;
  3.  
  4. BEGIN
  5. Mega.INVERT;
  6. Console.WriteStr(" To Be Inverted!");
  7. Mega.INVERT;
  8. Console.WriteStr(" To Be Inverted!");
  9. Mega.INVERT;
  10. END DemoInvert.
Работает хреновина! В следующих постах расскажу о процедурах с параметрами, там при сопряжении Си и Оберона есть тонкости.

Вопросы, предложения.


Вложения:
Комментарий к файлу: Демонстрационная программа DemoInvert
DemoInvert.tap [221 байт]
Скачиваний: 316
Комментарий к файлу: Исходники библиотеки Mega
LibMega.zip [4.96 КБ]
Скачиваний: 369
Вернуться к началу
 Профиль  
Ответить с цитатой  
 Заголовок сообщения: Re: DemoTiles.Mod
СообщениеДобавлено: 18 май 2014, 05:39 
Не в сети
Аватара пользователя

Сообщения: 843
Откуда: Днепропетровская обл.
Немного о структуре библиотек в ZXDev.

Символьный файл модуля GrTiles хранится в ZXDev/Lib/Sym/GrTiles.sym, и посмотреть его содержимое напрямую нельзя, т.к. у него двоичный формат. Но можно с помощью XDev -> Show Definition.
Интерфейсный модуль GrTiles хранится в файле ZXDev/Lib/Mod/GrTiles.Mod, но там нет ни строки реализации, только константы, типы и процедуры. Реализация этого модуля написана на встроенном в SDCC ассемблере и находится в файле ZXDev/Lib/C/GrTiles.c, который компилируется (по F11) скриптом ZXDev/Lib/Bin/compile.bat, если чёрненькое окошко мигнуло и исчезло, это значит, что ошибок нет. Иначе оно не исчезнет.

Обратите внимание на "линию заголовка" и "линию разреза":

/*--------------------------------- Cut here ---------------------------------*/

Это кастомная реализация смартлинковки, которая не предусмотрена во многих компиляторах Си. Самым разумным было бы добавить эту фичу во все сишные компиляторы, но мы-то этого сделать не можем. А такие компиляторы как, например, Turbo C уже вообще никто никогда не доработает. Поэтому остаётся лишь выходить из положения по-своему.

Сишный файл режется на кусочки по "линиям разреза", а всё, что расположено до "линии заголовка", включается в каждый разрезанный кусок. Это позволяет упаковывать в библиотеку кусочки кода процедур фрагментами, поэтому прилинковываться к целевому коду будут только реально использованные процедуры.

Логика сборки модуля (по F12) в библиотеку (обычно с таким разрезанием на кусочки) описывается скриптом ZXDev/Lib/Bin/build.bat, но, поскольку часто нужно учитывать какие-то специфические особенности (например, то, что модуль GrTiles находится в библиотеке XDev.lib), предусмотрена возможность помимо скрипта сборки по умолчанию использовать скрипт, адаптированный к особенностям сборки каждого модуля и имеющий более высокий приоритет. Такой скрипт нужно назвать одноименно с модулем и поместить в ZXDev/Lib/Obj (GrTiles.bat для GrTiles.Mod).


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 19 июн 2014, 04:34 
Не в сети

Сообщения: 25
Вопрос 1. Стоит ли на тратить время на написание таких обёрток?

Изучаю ассемблер, и что-то не вижу, как вставить бинарник. Нашёл только директиву
include /string/
Которая включает ассемблерный файл.
Это получается, что ресурсный файл со спрайтами придётся готовить в виде ассемблерных текстов с кучей .db .dw :(
И как передать имя файла в ассемлер из оберона? Хотя есть вариант завести фиктивную переменную, а потом её имя в предпроцессоре Си использовать как строку.
Вопрос 2. В том ли направлении я копаю по подключению ресурсов?

Так-же просмотрел ZXDev\Docu\sdccman.pdf пункт 3.14.2 и далее, там вроде как написано как стыковать сишные данные с ассемблерными вставками, но не впитал смысл с ходу. Много слов с двумя подчёркиваниями впереди, всё по неруски, и ассемблер для примера приводится не Z80, не так-то просто усвоить. А в свете обещания:
Цитата:
В следующих постах расскажу о процедурах с параметрами, там при сопряжении Си и Оберона есть тонкости.
,жду пока этого. :)


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 20 июн 2014, 18:36 
Не в сети
Аватара пользователя

Сообщения: 843
Откуда: Днепропетровская обл.
Здорово, что есть интерес к этому направлению, значит — продолжаем. :)

Reobne писал(а):
Вопрос 1. Стоит ли на тратить время на написание таких обёрток?
После такого замечательного достижения уже очевидно, что нет. :) Но я хотел бы ещё заметить, что Оберон-библиотеки будут чуть менее гибки, чем сишные, например, в плане настройки свойств конфигуратором (без перекомпиляции) или передачи беззнаковых значений знаковым параметрам, впрочем, об этом ниже. Нам нужно будет постепенно со всем этим разобраться. Пока же Си видится как хороший промежуточный формат для представления библиотек, позволяющий с помощью препроцессора реализовывать всякие интересные фокусы, которые были бы недостижимы в обычном, честном (сразу в машкод без всяких предварительных уровней) Оберон-компиляторе без серьёзной его переделки. В этом для нас, ретро-кодеров, есть большой плюс, и здесь Си — как раз на своём месте даже не как язык высокого уровня, а как инструмент предварительной обработки исходника и придания ему нужных качеств, которые нельзя придать обычными средствами Оберона. Впрочем, здесь и Си имеет свои ограничения.

А вот для описания интерфейсов Оберон подходит куда лучше, чем Си. Ведь программисту обычно нужен взгляд на модуль/библиотеку в целом, с большого. А не куча макросов вида #ifdef _Linux ..., характерных для сишных "интерфейсов", являющихся ничем иным как системной реализацией, причём в рамках Си-парадигмы — препроцессор: "это заменяется на это" вместо "функция" и т.п.

Соль межмодульного взаимодействия Оберон-программ — это интерфейсы, регламентирующие обмен данными и вызов кода. Возможности по описанию Оберон-интерфейсов ограничены, но я не стал бы считать это большим недостатком — самый скромный по своим возможностям Оберон-интерфейс с лихвой затыкает за пояс традиционные .DLL Windows и .SO Linux, он гибче "ОО"-интерфейсов Java. Доказательный факт — легко прицепиться и к коду на Java и .NET (из GPCP), и к готовым DLL/.SO (из BlackBox или, например, XDS).

Речь не о том, что Оберон-интерфейсы во всём "мощнее" сишных хидеров и могут поспособствовать всякому хитрому низкоуровневому трюкачеству. Речь о том, что они дают более адекватную, более высокоуровневую концепцию для описания межмодульных взаимодействий, к тому же и более компактную.

Для XDev в схеме трансляции через Ofront нужно описать интерфейс, т.е. все константы, типы, переменные, записи, процедуры (можно без тела). Странслировав этот описатель, устроенный как обычный модуль, мы получим символьный файл .sym (в формате Ofront'а). Также в /Obj (или /Lib/Obj) будет создано два сишных файла "реализации" — заголовок .h и тело .c.

Теперь у нас есть возможность писать ассемблерные вставки прямо в теле Оберон-модуля, но здесь есть ограничения, как я уже сказал. Такой пример:

Оберон-типы: SHORTINT, INTEGER, LONGINT. Как оказалось, Ofront проверяет константные значения, передаваемые (и присваиваемые) этим типам. Поэтому на Обероне не получится передать типу SHORTINT значение > 127. А так хочется, ведь это байт. Поэтому я иду на такую хитрость: объявляю Оберон-параметр как INTEGER, а Си-параметр — как unsigned char:
Код: "OBERON"
  1. PROCEDURE Border* (color: SHORTINT);
  2. BEGIN
  3. Asm.Code("LD A,4(IX)");
  4. Asm.Code("CALL 0x229B ");
  5. END Border;
Отличная процедура! Описывается в теле Оберон-модуля, работает и всё прекрасно. Но если попробовать ей передать параметр >127, то транслятор не пустит. Если описать здесь параметр как INTEGER, то получится менее эффективный код. Поэтому остаётся описать пустое тело:
Код: "OBERON"
  1. PROCEDURE Border* (color: INTEGER); END Border;
(обращаю внимание, что BEGIN, как выяснилось, необязателен)
И писать реализацию на Си, только параметр будет уже unsigned char, т.е. однобайтовый. Но поскольку MAX(INTEGER) у нас 32767, то можно будет присваивать ей значения >127, что нам и требуется.

Отсюда хитрости, когда тип Coords описывается как SHORTINT, а в параметрах используется INTEGER. Это не снижает качество кода. Это просто трюк, чтобы можно было описать переменные типа Coords как однобайтовые, но смочь передать в процедуру не только эти переменные (значение которых в рантайме конечно же не проверяется), но и числа-константы >127.

Корявенько чуть-чуть, но это плата за отсутствие в Оберон-стандарте беззнаковых типов. Мы с Олегом (Saferoll) планировали поработать в этом направлении, да как-то жизнь взяла своё. ;)

Reobne писал(а):
Вопрос 2. В том ли направлении я копаю по подключению ресурсов?
Вопрос заслуживает отдельной темы. :)


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 20 июн 2014, 20:33 
Не в сети
Аватара пользователя

Сообщения: 843
Откуда: Днепропетровская обл.
Заголовки процедур. __naked, фрейм Enter/Leave. В SDCC можно описывать процедуры с тегом __naked:
Код: "C"
/*--------------------------------- Cut here ---------------------------------*/
void _Laser_LB_SL1V (void) __naked {
__asm
LD DE,#__Laser_LB_035
JP __Laser_LB_056
__endasm;
} //_Laser_LB_SL1V
Для такой процедуры не генерируются команды фрейма Enter/Leave, выглядящие так:
Код: "ASM"
_AsmTest_Border_start::
_AsmTest_Border:
push ix ; фрейм Enter
ld ix,#0
add ix,sp
; ...
pop ix ; фрейм Leave
ret
_AsmTest_Border_end::
Для обычных процедур (Оберон-процедур) фрейм всегда генерируется. Чтобы от этого умно отказаться — нужно опять городить системные теги PROCEDURE [1] и т.д., т.е. то, что приходится делать и для указания модели вызова. В итоге можно и так, но пока что вопрос открыт.

Сишные же процедуры спокойно пишутся ручками, и с помощью __naked можно даже отказаться от генерации в конце функции команды RET. Кстати, эту возможность я обнаружил не так давно благодаря SfS и его библиотечке libspr.

А вот ещё симпатичный трючок, который можно применить для эффективности. Имеем такое объявление процедуры:
Код: "OBERON"
  1. PROCEDURE FONT* (addr: LONGINT); END FONT;
( LONGINT применяется из тех же соображений, т.е. если применить здесь INTEGER, то не даст присвоить значение >32767 ).
А вот сишная реализация, которая является фактически не вызовом процедуры, а присваиванием:
Код: "C"
#define Basic_FONT(fontAddr) (*(unsigned*) (0x5C36) = (fontAddr - 256))

Константы и их подстановка. Ofront всегда вычисляет и подставляет в тело транслируемой Си-программы константы. Это и хорошо, и (местами) плохо. Хорошо потому что быстрее транслирует сишный код. Плохо потому что менее наглядно и иногда такая короткая схема мешает более изощрённой логике работы с константой. Например, если константа в сишном хидере представляет собой сложное условие, зависящее от #ifdef'ов.

Предлагаю трюк под названием “подстановка константы”: описываем константу в Оберон-интерфейсе как переменную:
Код: "OBERON"
  1. CONST constcolor* = 4; (* Это в модуле Cfg *)
  2. VAR varcolor-: INTEGER; (* Неважно какое у неё значение - *)
  3. (* это фиктивная константа, которая реализуется на уровне Си *)
  4. (* Уже в другом модуле: *)
  5. b.BORDER(constcolor); (* транслируется в Basic_BORDER(4). *)
  6. b.BORDER(varcolor); (* транслируется в Basic_BORDER(Cfg_varcolor) *)
Этим способом предлагаю вынести константы из уровня Оберона на уровень Си, чтобы взаимодействовать с конфигураторами (.h), в которых они описаны, т.е. достигается эффект “без перекомпиляции”. Ну, вобщем, можно придумать применение этому трюку, я его уже пару раз использовал.

Передача строковых параметров. Ofront всегда имеет в виду длину строки и передаёт её процедурам вторым параметром после указателя на строку (то самое str и str__len). Длина всегда доступна с помощью встроенной функции LEN(str). И это незаменимо для того, чтобы никогда-никогда не выйти за границы памяти, отведённой под строку. Если передаётся указатель на строку, то он указывает на структуру, первый элемент которой — длина строки, дальше сама строка. Я рассматривал возможность для “маленьких” платформ (а также для взаимодействия с WinAPI) сделать какую-то поддержку работы с “опасными” строками в духе Си, но Йозеф Темпл (автор Ofront’а) не одобрил, сказал, что это шаг назад. :) Хотя можно сделать конечно.

Что ещё. Ага. Указатели. Они обычно присваиваются транслятором автоматически в NIL, что может вызвать дополнительные (скрытые) накладные расходы. Остаётся возможность работать с указателями через числовой тип подходящей разрядности и доступ к памяти с помощью Basic.POKE/PEEK или SYSTEM.PUT/GET. “Волшебный” переход адреса от MAX(ADDRESS) к MIN(ADDRESS), т.е. в случае двухбайтного слова — от 32767 (7FFFH) к -32768 (= 8000H == 32768) осуществляется безкровно, хотя обсуждаем вопрос: ловить ли прерыванием (или ASSERT’ом) такое варварство как недопустимое переполнение. Можно и ловить, но Си (и Оберон в варианте Ofront) не ловит, да и многие другие Оберон-реализации не ловят, а которые ловят (BlackBox), даже там эта возможность отключена по умолчанию. Так что имеем знаковый, но как бы беззнаковый адресный тип и им активно пользуемся. :)

Не всё из задуманного реализовано, но я подошёл к такому устроению универсального батника для сборки библиотеки, опишу его логику.

Итак, символьный файл, сишные заголовок и тело сгенерированы. Теперь скрипту надо решить, это Оберон-библиотека, т.е. транслировать ли автоматически сгенерированный Ofront’ом файл, или же альтернативную сишную реализацию написанного Оберон-интерфейса.

Автоматически сгенерированные Ofront’ом хедеры/тела хранятся в /Lib/Obj, их сишные реализации — в /Lib/C

Так что сперва проверяется, существует ли /Lib/C/Module.c, и если да, то считается, что в Lib/Obj находится “пустая” фиктивная реализация модуля, которая нам не нужна и порождена только как побочный результат генерации нужного нам символьного файла. Поэтому компилируется сишный файл /Lib/C/Module.c. В случае же отсутствия оного компилируется сгенерированный Ofront’ом /Lib/Obj/Module.c. Примерно так же может быть устроен и механизм подключения хидера (из какой папки его брать — /Lib/C/Module.h или /Lib/Obj/Module.h).

Ещё надо заметить, что пока что есть смысл Оберон-модули с телом на Обероне компилировать по умолчанию без смартлинковки, потому что её пока можно применять только для простых модулей с процедурами. Если в модуле есть переменные или записи, методы, то разрезание уже не будет работать правильно. Дальнейшее совершенствование этого направления отложено на будущее.


Вернуться к началу
 Профиль  
Ответить с цитатой  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 5 ] 

Часовой пояс: UTC + 2 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
Создано на основе phpBB® Forum Software © phpBB Group
Тех.поддержка phpBB