Обновить
-8
2.6

Пользователь

Отправить сообщение

Какой ценой и как именно разработчики из немецкой демогруппы Farbrausch смогли сжать до 96 килобайт пусть и небольшую, но технически современную на тот момент игру?

Именно ответа на этот вопрос ожидал от статьи, прочитав заголовок. С разбором алгоритмов, форматов данных, хитрых уловок и внезапных компромиссов.

Но, увы, вместо этого получил «Ну, они разработали набор инструментов, вот вам скриншоты в качестве доказательства. А вот ещё у них другие демки были, посмотрите ютубчик».

Вы нашли в этом здравые мысли? Вот уж действительно нонсенс.

Лицо, выявившее недостатки безопасного использования
программы для ЭВМ и (или) базы данных, обязано сообщить о них
правообладателю в течение пяти рабочих дней

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

Если человек захочет, то он научится правильно произносить. И не важно, первый, второй или шестьдесят пять тысяч пятьсот тридцать шестой это у него язык.

Если уж на то пошло, произношение и написание современного английского языка имеет мало общего. И ничего, все справляются без особых проблем.

даже те кто должен знать как это написанное по английски правильно произносить не смогут это сделать

Сериоусли? И донт хавэ эни проблем витх Борсч!

Златая цепь на Дубе том...

Предлагаю развить тему и локализовать роли:

  • Junior — Иван-дурак

  • Senior — Богатырь

  • Team Lead — Дядька Черномор, для краткости — просто «Черномор»

  • Project Manager — Стряпчий

коннекторы

Ну вы что, какие ещё «коннекторы»? «Скрепы» — наше всё!

Коммерческий запуск «Яги» запланирован

Кажется, здесь забыли частицу «не» перед «запланирован» и точку — после.

print("Кому три — подходите с зачётками!\n")

Лучший интерфейс - язык Python; каждый может настроить кастомизацию чего угодно под себя.

Прямо-таки «чего угодно»?
Как кастомизировать питон так, чтобы не было значащих отступов, а вместо них структура задавалась фигурными скобками?

А можно хоть какой-то список таких программ?

DropBox, Skype, Slack и т.д. и т.п.
Современный Adobe Photoshop, похоже, тоже. Node.JS он использует точно.
Далеко не полный список: https://en.wikipedia.org/wiki/List_of_software_using_Electron

Если при запуске программы в Диспетчере Задач появляется несколько процессов этого приложения, запущенных с параметрами командной строки -type=... и --user-data-dir=..., то это, с большой долей вероятности, программа, написаная с использованием Electron, а значит и JavaScript.

Просто у меня нет джавы на компах совсем

Java и JavaScript это совершенно разные языки, это раз.
Как пользователь вы, скорее всего, понятия не имеете что и как используют программы, у вас установленные, это два. Даже в Adobe Acrobat Reader есть интерпретатор JS с незапамятных времён.

(разве что в браузере, но я ими не пользуюсь)

А этот комментарий вы, видимо, набирали на телетайпе при свете лучины.

Lenovo B570e, Core i5-2420M, 8Гб, MSATA SSD 64G - всё отлично работает

Если не пользоваться современными браузерами, то 8 гигабайтов может и хватить. Иначе -- увы.

Мне кажется тут вопрос исключительно психологический, у разных людей разная нервная система отсюда разное восприятие тормозов.

Ну, это вы уже какую-то дичь задвигать начали.

Минул уж год, что там с «Галопом»?

Я пытался сначала сделать через них, но я не нашел удобных инструментов для замены байткода.

В ASM есть пакет org.objectweb.asm.tree, который делает именно то, что вы хотите. Он позволяет получить список инструкций байткода в методе, обработать его нужным образом и затем сохранить изменённый class-файл.

NB: Этот пакет нужно подключать в проект как отдельную зависимость, org.ow2.asm:asm-tree:<версия>, в дополнение к основному org.ow2.asm:asm:<версия>.

Для примера можем взять упрощённую задачу из этой статьи. Будем заменять статические вызовы scout.definition.Keys::create(Class<?>) на числовые константы. Константы будем присваивать в порядке появления вызова метода в байткоде.

Hidden text
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class BytecodePatcher {

    static class MethodPatcher extends MethodNode {

        private static final String CLASS_NAME = "scout/definition/Keys";

        private static final String METHOD_NAME = "create";

        private static final String METHOD_DESCRIPTOR = "(Ljava/lang/Class;)I";

        private final MethodVisitor methodVisitor;

        private final AtomicInteger typeCounter;

        private final Map<String, Integer> typeMap;

        public MethodPatcher(
            int api,
            int access,
            String name,
            String descriptor,
            String signature,
            String[] exceptions,
            AtomicInteger typeCounter,
            Map<String, Integer> typeMap,
            MethodVisitor methodVisitor
        ) {
            super(api, access, name, descriptor, signature, exceptions);
            this.methodVisitor = methodVisitor;
            this.typeCounter = typeCounter;
            this.typeMap = typeMap;
        }

        @Override
        public void visitEnd() {
            super.visitEnd();
            patchCode(this.instructions);
            this.accept(this.methodVisitor);
        }

        private void patchCode(InsnList instructions) {
            ListIterator<AbstractInsnNode> iterator = instructions.iterator();
            while (iterator.hasNext()) {
                int sort;
                AbstractInsnNode current = iterator.next();
                if (current instanceof LdcInsnNode ldc && ldc.cst instanceof Type type
                    && ((sort = type.getSort()) == Type.OBJECT || sort == Type.ARRAY)
                    && isFactoryMethodCall(current.getNext())
                ) {
                    iterator.remove();

                    current = iterator.next();
                    assert
                        current instanceof MethodInsnNode
                        : "Internal error: MethodInsnNode expected, but got " + current.getClass() + " instead!";

                    int opcode;
                    int typeIndex = getTypeIndex(type.getDescriptor());
                    if (typeIndex <= Byte.MAX_VALUE) {
                        opcode = Opcodes.BIPUSH;
                    } else if (typeIndex <= Short.MAX_VALUE) {
                        opcode = Opcodes.SIPUSH;
                    } else {
                        throw new RuntimeException("typeIndex overflow: " + typeIndex);
                    }
                    iterator.set(new IntInsnNode(opcode, typeIndex));
                }
            }
        }

        private boolean isFactoryMethodCall(AbstractInsnNode instruction) {
            return
                instruction instanceof MethodInsnNode methodInsn
                && methodInsn.getOpcode() == Opcodes.INVOKESTATIC
                && CLASS_NAME.equals(methodInsn.owner)
                && METHOD_NAME.equals(methodInsn.name)
                && METHOD_DESCRIPTOR.equals(methodInsn.desc);
        }

        private int getTypeIndex(String typeDescriptor) {
            return typeMap.computeIfAbsent(typeDescriptor, unused -> typeCounter.getAndIncrement());
        }
    }

    static class ClassPatcher extends ClassVisitor {

        private final AtomicInteger typeCounter = new AtomicInteger();

        private final Map<String, Integer> typeMap = new HashMap<>();

        protected ClassPatcher(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        public Map<String, Integer> getTypeMap() {
            return this.typeMap;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
            return new MethodPatcher(
                this.api, access, name, descriptor, signature, exceptions, this.typeCounter, this.typeMap, methodVisitor
            );
        }

    }

    private static byte[] patch(byte[] classBytes) {
        ClassReader classReader = new ClassReader(classBytes);
        ClassWriter classWriter = new ClassWriter(classReader, 0);
        ClassPatcher classPatcher = new ClassPatcher(Opcodes.ASM9, classWriter);
        classReader.accept(classPatcher, 0);

        dumpTypeMapping(classPatcher.getTypeMap());

        return classWriter.toByteArray();
    }

    private static void dumpTypeMapping(Map<String, Integer> typeMap) {
        System.out.println(" +-------+----------------------------------+");
        System.out.println(" | Index | Type                             |");
        System.out.println(" +-------+----------------------------------+");

        System.out.println(typeMap
            .entrySet()
            .stream()
            .sorted(Comparator.comparingInt(Map.Entry::getValue))
            .map(entry -> String.format(" | % 5d | %-32s |", entry.getValue(), entry.getKey()))
            .collect(Collectors.joining("\n +-------+----------------------------------+\n")));

        System.out.println(" +-------+----------------------------------+");
    }

    public static void main(String... args) throws IOException {
        if (args.length < 1) {
            System.err.println("Usage:");
            System.err.println("  java BytecodePatcher <source class file> [<patched class file>]");
            System.err.println();
            System.exit(-1);
        }

        byte[] classBytes = Files.readAllBytes(Path.of(args[0]));
        byte[] patchedBytes = patch(classBytes);

        if (args.length > 1) {
            Files.write(Path.of(args[1]), patchedBytes);
        } else {
            System.out.println("No output file specified!");
        }
    }
}

Всего полторы сотни строк, не считая импортов и обвязки.

Для Class эти функции не такие быстрые, как хотелось бы.

У Class и equals() и hashCode() наследуются от java.lang.Object.
Куда уж быстрее-то?

Прежде чем перейдём к устройству байт-кода, расскажу о том, что такое обратная польская запись (ОПЗ).

Вспоминается анекдот № 301205. Шучу, если что.

System.out.println(1 + 2 * 3);

Стоило отдельно уточнить, что в реальном мире значение выражения было бы вычислено ещё во время компиляции и в байткоде на стек будет загружено сразу значение 7.

И не инструкцией ldc, а инструкцией bipush.

IMULT

«T» здесь лишнее.

Для этого подойдёт SIPUSH-инструкция: ... Минус в том, что её аргумент ограничен 2 байтами, поэтому значения не могут быть больше 2^15 (32 768)

Я  слышал, что на собеседованиях в Яндекс есть алгоритмическая секция ;]

For short, from -32768 to 32767, inclusive
JLS 17 § 4.2.1

 

Во всей этой магии по замене инструкций нам сильно поможет библиотека BCEL (Byte Code Engineering Library).

BCEL в 2023 году?
Закопайте стюардессу и возьмите хотя бы ASM или модный-молодёжный Byte Buddy.

Она предоставляет очень удобный API

Очень удобный???!!!
Да вы троллите!

Вы где смотрели?
Вот же они все: https://github.com/paintdotnet/release/tags
Пара-тройка кликов с официального сайта и вы там. И вам в любом случае качать релиз с GitHub, если нужен нормальный offline installer.

Ещё бы она не была непопулярна.
Даже для того, чтобы продолжить нормально пользоваться консолью после установки приходится лезть сначала в Гугл, а потом в настройки. В противном случае у нас в окне будут на хрен не нужные вкладки вместо заголовка, а на Alt-F4 и F11 консоль будет реагировать так, словно это говноприложение на «Электроне», а не старый-добрый Far Manager.

Про нефункциональные меню «Пуск», таскбар и невидимые полосы прокрутки даже и не говорю.

Благо, что у производителей ноутбуков обычно есть опция откатиться на предыдущую версию винды.

Толку-то?
Их миллион лет назад просили сделать раскладку с полноразмерными курсорными клавишами и столбцом функциональных (Home/End/PgUp/PgDn) справа.

А-ля такую

Сделали? Хрена с два!
Стрелки всё так же кастрированые, функциональные клавиши -- через Fn.

У тебя сенсорный планшет с виндовс или ноут с тачпадом, а скроллбар млипиздрический и требует пиксельхантинга.

Давным-давно, когда Sony ещё делала ноутбуки, тачпады их Sony Vaio поддерживали жесты, в том числе и прокрутку. Горизонтальной я не пользовался, а вот вертикальной (вверх/вниз) -- очень даже.

Проводишь пальцем вдоль правой кромки тачпада сверху вниз и, не отрывая, переводишь его в любое удобное место. После чего накручиваешь спираль по часовой или против часовой стрелки. Адски удобно!

Это логически выводимый мем:


Ализари́н (1,2-дигидроксиантрахинон) — органическое соединение, производное антрахинона с химической формулой C14H8O4.… С древности применялся как протравный краситель по ткани.

alizar был пионером потока желтушных публикаций на Хабрахабре.
Знамя из его слабеющих рук подхватила «Информационная служба Хабра» со 100500 сотрудников, а он это вывозил в одну калитку.
Человечище!

Вы что, котофоб!?

ОБС: Сотрудники информационной службы Хабра могут обойтись без желтушных заголовков, но тогда на работе придётся работать.

очень напрягало когда телевизор после такого долго загружался.

А должно было напрягать то, что вместо телевизора у вас какое-то г-но, которому зачем-то нужно загружаться. А потом ещё и блютусом во все стороны светить, наверняка.

видимо расчет на иностранцев или тех, кто в приложения на смартфоне не умеет.

Скорее, расчёт на закон больших чисел )


Заказать такси можно и по телефону, хоть Йандекс и пытается принудить всех пользоваться приложением или глючным сайтом, написанным левой задней ногой с полным презрением к юзабилити. И даже до живого оператора вместо робота добраться можно.

Информация

В рейтинге
1 340-й
Зарегистрирован
Активность